From 9bd41a8fcb17028392dc410a48d3bf5954d44aaa Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 21 Mar 2024 11:21:49 +0000 Subject: [PATCH 01/78] Updated methods --- src/ConvertKit_API.php | 161 +++++++++++++++++++++++------------------ 1 file changed, 90 insertions(+), 71 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 3075af4..0359a50 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -510,17 +510,26 @@ public function get_sequence_subscriptions( } /** - * Gets all tags. + * Gets tags * - * @since 1.0.0 + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * - * @see https://developers.convertkit.com/#list-tags + * @see https://developers.convertkit.com/v4.html#list-tags * * @return false|mixed */ - public function get_tags() + public function get_tags(string $after_cursor = '', string $before_cursor = '', int $per_page = 100) { - return $this->get_resources('tags'); + return $this->get( + endpoint: 'tags', + args: $this->build_pagination_params( + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) + ); } /** @@ -530,7 +539,7 @@ public function get_tags() * * @since 1.0.0 * - * @see https://developers.convertkit.com/#create-a-tag + * @see https://developers.convertkit.com/v4.html#create-a-tag * * @return false|mixed */ @@ -539,7 +548,7 @@ public function create_tag(string $tag) return $this->post( 'tags', [ - 'tag' => ['name' => $tag], + 'name' => $tag, ] ); } @@ -548,14 +557,15 @@ public function create_tag(string $tag) * Creates multiple tags. * * @param array $tags Tag Names. + * @param bool|string $callback_url URL to notify for large batch size when async processing complete. * * @since 1.1.0 * - * @see https://developers.convertkit.com/#create-a-tag + * @see https://developers.convertkit.com/v4.html#bulk-create-tags * * @return false|mixed */ - public function create_tags(array $tags) + public function create_tags(array $tags, string $callback_url = false) { // Build API compatible array of tags. $apiTags = []; @@ -565,9 +575,13 @@ public function create_tags(array $tags) ]; } + // Send request. return $this->post( - 'tags', - ['tag' => $apiTags] + 'bulk/tags', + [ + 'tag' => $apiTags, + 'callback_url' => $callback_url, + ] ); } @@ -576,60 +590,31 @@ public function create_tags(array $tags) * * @param integer $tag_id Tag ID. * @param string $email Email Address. - * @param string $first_name First Name. - * @param array $fields Custom Fields. * - * @see https://developers.convertkit.com/#tag-a-subscriber + * @see https://developers.convertkit.com/v4.html#tag-a-subscriber-by-email-address * * @return false|mixed */ - public function tag_subscriber( - int $tag_id, - string $email, - string $first_name = '', - array $fields = [] - ) { - // Build parameters. - $options = ['email' => $email]; - - if (!empty($first_name)) { - $options['first_name'] = $first_name; - } - if (!empty($fields)) { - $options['fields'] = $fields; - } - - // Send request. + public function tag_subscriber(int $tag_id, string $email) { return $this->post( - sprintf('tags/%s/subscribe', $tag_id), - $options + endpoint: sprintf('tags/%s/subscribe', $tag_id), + args: ['email_address' => $email] ); } /** - * Adds a tag to a subscriber. - * - * @param integer $tag Tag ID. - * @param array $options Array of user data. + * Tags a subscriber by subscriber ID with the given existing Tag. * - * @deprecated 1.0.0 Use tag_subscriber($tag_id, $email, $first_name, $fields). + * @param integer $tag_id Tag ID. + * @param string $email Email Address. * - * @see https://developers.convertkit.com/#tag-a-subscriber + * @see https://developers.convertkit.com/v4.html#tag-a-subscriber * - * @return false|object + * @return false|mixed */ - public function add_tag(int $tag, array $options) + public function tag_subscriber_by_id(int $tag_id, int $subscriber_id) { - // This function is deprecated in 1.0, as we prefer functions with structured arguments. - trigger_error( - 'add_tag() is deprecated in 1.0. Use tag_subscribe($tag_id, $email, $first_name, $fields) instead.', - E_USER_NOTICE - ); - - return $this->post( - sprintf('tags/%s/subscribe', $tag), - $options - ); + return $this->post(sprintf('tags/%s/subscribers/%s', $tag_id, $subscriber_id)); } /** @@ -640,13 +625,13 @@ public function add_tag(int $tag, array $options) * * @since 1.0.0 * - * @see https://developers.convertkit.com/#remove-tag-from-a-subscriber + * @see https://developers.convertkit.com/v4.html#remove-tag-from-subscriber * * @return false|mixed */ public function remove_tag_from_subscriber(int $tag_id, int $subscriber_id) { - return $this->delete(sprintf('subscribers/%s/tags/%s', $subscriber_id, $tag_id)); + return $this->delete(sprintf('tags/%s/subscribers/%s', $tag_id, $subscriber_id)); } /** @@ -657,43 +642,77 @@ public function remove_tag_from_subscriber(int $tag_id, int $subscriber_id) * * @since 1.0.0 * - * @see https://developers.convertkit.com/#remove-tag-from-a-subscriber-by-email + * @see https://developers.convertkit.com/v4.html#remove-tag-from-subscriber-by-email-address * * @return false|mixed */ public function remove_tag_from_subscriber_by_email(int $tag_id, string $email) { - return $this->post( - sprintf('tags/%s/unsubscribe', $tag_id), - ['email' => $email] + return $this->delete( + sprintf('tags/%s/subscribers', $tag_id), + ['email_address' => $email] ); } /** - * List subscriptions to a tag + * List subscribers for a tag * - * @param integer $tag_id Tag ID. - * @param string $sort_order Sort Order (asc|desc). - * @param string $subscriber_state Subscriber State (active,cancelled). - * @param integer $page Page. + * @param integer $tag_id Tag ID. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $tagged_after Filter subscribers who have been tagged after this date. + * @param \DateTime $tagged_before Filter subscribers who have been tagged before this date. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * - * @see https://developers.convertkit.com/#list-subscriptions-to-a-tag + * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-tag * * @return false|mixed */ public function get_tag_subscriptions( int $tag_id, - string $sort_order = 'asc', string $subscriber_state = 'active', - int $page = 1 + \DateTime $created_after = null, + \DateTime $created_before = null, + \DateTime $tagged_after = null, + \DateTime $tagged_before = null, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 ) { + // Build parameters. + $options = []; + + if (!empty($subscriber_state)) { + $options['status'] = $subscriber_state; + } + if (!is_null($created_after)) { + $options['created_after'] = $created_after->format('Y-m-d'); + } + if (!is_null($created_before)) { + $options['created_before'] = $created_before->format('Y-m-d'); + } + if (!is_null($added_after)) { + $options['added_after'] = $added_after->format('Y-m-d'); + } + if (!is_null($added_before)) { + $options['added_before'] = $added_before->format('Y-m-d'); + } + + // Build pagination parameters. + $options = $this->build_pagination_params( + params: $options, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ); + + // Send request. return $this->get( - sprintf('tags/%s/subscriptions', $tag_id), - [ - 'sort_order' => $sort_order, - 'subscriber_state' => $subscriber_state, - 'page' => $page, - ] + endpoint: sprintf('tags/%s/subscribers', $tag_id), + args: $options ); } From a47d0e956b174f1026eb5051a07834c6b4ad1b62 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 21 Mar 2024 14:15:38 +0000 Subject: [PATCH 02/78] Started tests --- src/ConvertKit_API.php | 31 ++++---- tests/ConvertKitAPITest.php | 146 ++++++++++++++++++++---------------- 2 files changed, 100 insertions(+), 77 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 0359a50..7dbfde2 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -546,8 +546,8 @@ public function get_tags(string $after_cursor = '', string $before_cursor = '', public function create_tag(string $tag) { return $this->post( - 'tags', - [ + endpoint: 'tags', + args: [ 'name' => $tag, ] ); @@ -557,7 +557,7 @@ public function create_tag(string $tag) * Creates multiple tags. * * @param array $tags Tag Names. - * @param bool|string $callback_url URL to notify for large batch size when async processing complete. + * @param string $callback_url URL to notify for large batch size when async processing complete. * * @since 1.1.0 * @@ -565,23 +565,26 @@ public function create_tag(string $tag) * * @return false|mixed */ - public function create_tags(array $tags, string $callback_url = false) + public function create_tags(array $tags, string $callback_url = '') { - // Build API compatible array of tags. - $apiTags = []; + // Build parameters. + $options = [ + 'tags' => [], + ]; foreach ($tags as $i => $tag) { - $apiTags[] = [ + $options['tags'] = [ 'name' => (string) $tag, ]; } + if (!empty($callback_url)) { + $options['callback_url'] = $callback_url; + } + // Send request. return $this->post( - 'bulk/tags', - [ - 'tag' => $apiTags, - 'callback_url' => $callback_url, - ] + endpoint: 'bulk/tags', + args: $options ); } @@ -597,7 +600,7 @@ public function create_tags(array $tags, string $callback_url = false) */ public function tag_subscriber(int $tag_id, string $email) { return $this->post( - endpoint: sprintf('tags/%s/subscribe', $tag_id), + endpoint: sprintf('tags/%s/subscribers', $tag_id), args: ['email_address' => $email] ); } @@ -612,7 +615,7 @@ public function tag_subscriber(int $tag_id, string $email) { * * @return false|mixed */ - public function tag_subscriber_by_id(int $tag_id, int $subscriber_id) + public function tag_subscriber_by_subscriber_id(int $tag_id, int $subscriber_id) { return $this->post(sprintf('tags/%s/subscribers/%s', $tag_id, $subscriber_id)); } diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 9391fcd..0c9e2c4 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -905,13 +905,14 @@ public function testGetSequenceSubscriptionsWithInvalidSequenceID() */ public function testGetTags() { - $this->markTestIncomplete(); - $result = $this->api->get_tags(); - $this->assertIsArray($result); - // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. - $tag = get_object_vars($result[0]); + // Assert sequences and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Check first tag in resultset has expected data. + $tag = get_object_vars($result->tags[0]); $this->assertArrayHasKey('id', $tag); $this->assertArrayHasKey('name', $tag); $this->assertArrayHasKey('created_at', $tag); @@ -926,17 +927,15 @@ public function testGetTags() */ public function testCreateTag() { - $this->markTestIncomplete(); - $tagName = 'Tag Test ' . mt_rand(); $result = $this->api->create_tag($tagName); // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. - $tag = get_object_vars($result); + $tag = get_object_vars($result->tag); $this->assertArrayHasKey('id', $tag); $this->assertArrayHasKey('name', $tag); $this->assertArrayHasKey('created_at', $tag); - $this->assertEquals($tag['name'], $tagName); + $this->assertEquals($tag['name'], $tagName); } /** @@ -949,8 +948,6 @@ public function testCreateTag() */ public function testCreateTagBlank() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $result = $this->api->create_tag(''); } @@ -965,8 +962,6 @@ public function testCreateTagBlank() */ public function testCreateTagThatExists() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $result = $this->api->create_tag($_ENV['CONVERTKIT_API_TAG_NAME']); } @@ -980,8 +975,6 @@ public function testCreateTagThatExists() */ public function testCreateTags() { - $this->markTestIncomplete(); - $tagNames = [ 'Tag Test ' . mt_rand(), 'Tag Test ' . mt_rand(), @@ -989,7 +982,7 @@ public function testCreateTags() $result = $this->api->create_tags($tagNames); // Iterate through the results to confirm the tags were created. - foreach ($result as $i => $tag) { + foreach ($result->tags as $i => $tag) { // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. $tag = get_object_vars($tag); $this->assertArrayHasKey('id', $tag); @@ -1009,8 +1002,6 @@ public function testCreateTags() */ public function testCreateTagsBlank() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $result = $this->api->create_tags([ '', @@ -1028,8 +1019,6 @@ public function testCreateTagsBlank() */ public function testCreateTagsThatExist() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $result = $this->api->create_tags([ $_ENV['CONVERTKIT_API_TAG_NAME'], @@ -1046,73 +1035,104 @@ public function testCreateTagsThatExist() */ public function testTagSubscriber() { - $this->markTestIncomplete(); - $result = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $this->generateEmailAddress() + email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscription', get_object_vars($result)); + $this->assertArrayHasKey('subscriber', get_object_vars($result)); + $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); + $this->assertEquals( + get_object_vars($result->subscriber)['email_address'], + $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + ); } /** - * Test that tag_subscriber() returns the expected data - * when a first_name parameter is included. + * Test that tag_subscriber() throws a ClientException when an invalid + * tag is specified. * - * @since 1.0.0 + * @since 2.0.0 * * @return void */ - public function testTagSubscriberWithFirstName() + public function testTagSubscriberWithInvalidTagID() { - $this->markTestIncomplete(); - - $emailAddress = $this->generateEmailAddress(); - $firstName = 'First Name'; + $this->expectException(ClientException::class); $result = $this->api->tag_subscriber( - tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $emailAddress, - first_name: $firstName + tag_id: 12345, + email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); - - $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscription', get_object_vars($result)); - - // Fetch subscriber from API to confirm the first name was saved. - $subscriber = $this->api->get_subscriber($result->subscription->subscriber->id); - $this->assertEquals($subscriber->subscriber->email_address, $emailAddress); - $this->assertEquals($subscriber->subscriber->first_name, $firstName); } /** - * Test that tag_subscriber() returns the expected data - * when custom field data is included. + * Test that tag_subscriber() throws a ClientException when an invalid + * email address is specified. * - * @since 1.0.0 + * @since 2.0.0 * * @return void */ - public function testTagSubscriberWithCustomFields() + public function testTagSubscriberWithInvalidEmailAddress() { - $this->markTestIncomplete(); - + $this->expectException(ClientException::class); $result = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $this->generateEmailAddress(), - first_name: 'First Name', - fields: [ - 'last_name' => 'Last Name', - ] + email: 'not-an-email-address' ); + } - // Check subscription object returned. + /** + * Test that tag_subscriber_by_subscriber_id() returns the expected data. + * + * @since 2.0.0 + * + * @return void + */ + public function testTagSubscriberByID() + { + $result = $this->api->tag_subscriber_by_subscriber_id( + tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], + subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] + ); $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscription', get_object_vars($result)); + $this->assertArrayHasKey('subscriber', get_object_vars($result)); + $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); + $this->assertEquals(get_object_vars($result->subscriber)['id'], $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); + } - // Fetch subscriber from API to confirm the custom fields were saved. - $subscriber = $this->api->get_subscriber($result->subscription->subscriber->id); - $this->assertEquals($subscriber->subscriber->fields->last_name, 'Last Name'); + /** + * Test that tag_subscriber_by_subscriber_id() throws a ClientException when an invalid + * sequence ID is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testTagSubscriberByIDWithInvalidTagID() + { + $this->expectException(ClientException::class); + $result = $this->api->tag_subscriber_by_subscriber_id( + tag_id: 12345, + subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] + ); + } + + /** + * Test that tag_subscriber_by_subscriber_id() throws a ClientException when an invalid + * email address is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testTagSubscriberByIDWithInvalidSubscriberID() + { + $this->expectException(ClientException::class); + $result = $this->api->tag_subscriber_by_subscriber_id( + tag_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], + subscriber_id: 12345 + ); } /** @@ -1124,16 +1144,14 @@ public function testTagSubscriberWithCustomFields() */ public function testRemoveTagFromSubscriber() { - $this->markTestIncomplete(); - // Tag the subscriber first. $result = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $this->generateEmailAddress() + email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); // Get subscriber ID. - $subscriberID = $result->subscription->subscriber->id; + $subscriberID = $result->subscriber->id; // Remove tag from subscriber. $result = $this->api->remove_tag_from_subscriber( @@ -1143,6 +1161,8 @@ public function testRemoveTagFromSubscriber() // Confirm that the subscriber no longer has the tag. $result = $this->api->get_subscriber_tags($subscriberID); + var_dump($result); + die(); $this->assertIsArray($result->tags); $this->assertEmpty($result->tags); } From bf6c5d40b66888805d218b7b774d65093e410be7 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 21 Mar 2024 14:36:23 +0000 Subject: [PATCH 03/78] Started work on subscriber methods --- src/ConvertKit_API.php | 213 +++++++++++++++++++++++++++++++++-------- 1 file changed, 171 insertions(+), 42 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 3075af4..9ffedab 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -812,6 +812,148 @@ public function get_resources(string $resource) return $_resource; } + /** + * Get subscribers. + * + * @since 2.0.0 + * + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param string $email_address Search susbcribers by email address. This is an exact match search. + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. + * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. + * @param string $sort_field Sort Field (id|updated_at|cancelled_at). + * @param string $sort_order Sort Order (asc|desc). + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @see https://developers.convertkit.com/v4.html#list-subscribers + * + * @return false|mixed + */ + public function get_subscribers( + string $subscriber_state = 'active', + string $email_address = '', + \DateTime $created_after = null, + \DateTime $created_before = null, + \DateTime $updated_after = null, + \DateTime $updated_before = null, + string $sort_field = 'id', + string $sort_order = 'desc', + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) + { + // Build parameters. + $options = []; + + if (!empty($subscriber_state)) { + $options['status'] = $subscriber_state; + } + if (!empty($email_address)) { + $options['email_address'] = $email_address; + } + if (!is_null($created_after)) { + $options['created_after'] = $created_after->format('Y-m-d'); + } + if (!is_null($created_before)) { + $options['created_before'] = $created_before->format('Y-m-d'); + } + if (!is_null($updated_after)) { + $options['updated_after'] = $updated_after->format('Y-m-d'); + } + if (!is_null($updated_before)) { + $options['updated_before'] = $updated_before->format('Y-m-d'); + } + if (!empty($sort_field)) { + $options['sort_field'] = $sort_field; + } + if (!empty($sort_order)) { + $options['sort_order'] = $sort_order; + } + + // Build pagination parameters. + $options = $this->build_pagination_params( + params: $options, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ); + + // Send request. + return $this->get( + endpoint: 'subscribers', + args: $options + ); + } + + /** + * Create a subscriber. + * + * Behaves as an upsert. If a subscriber with the provided email address does not exist, + * it creates one with the specified first name and state. If a subscriber with the provided + * email address already exists, it updates the first name. + * + * @since 2.0.0 + * + * @param string $email_address Email Address. + * @param string $first_name First Name. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param array $fields Custom Fields. + * + * @see https://developers.convertkit.com/v4.html#create-a-subscriber + * + * @return mixed + */ + public function create_subscriber( + string $email_address, + string $first_name = '', + string $subscriber_state = '', + array $fields = [] + ) + { + // Build parameters. + $options = []; + + if (!empty($first_name)) { + $options['first_name'] = $first_name; + } + if (!empty($state)) { + $options['state'] = $state; + } + if (count($fields)) { + $options['fields'] = $fields; + } + + // Send request. + return $this->post( + endpoint: 'subscribers', + args: $options + ); + } + + public function create_subscribers(array $subscribers) + { + // @TODO. + } + + /** + * Get subscriber by id + * + * @param integer $subscriber_id Subscriber ID. + * + * @see https://developers.convertkit.com/v4.html#get-a-subscriber + * + * @return false|integer + */ + public function get_subscriber(int $subscriber_id) + { + return $this->get(sprintf('subscribers/%s', $subscriber_id)); + } + /** * Get the ConvertKit subscriber ID associated with email address if it exists. * Return false if subscriber not found. @@ -820,16 +962,12 @@ public function get_resources(string $resource) * * @throws \InvalidArgumentException If the email address is not a valid email format. * - * @see https://developers.convertkit.com/#list-subscribers + * @see https://developers.convertkit.com/v4.html#get-a-subscriber * * @return false|integer */ public function get_subscriber_id(string $email_address) { - if (!filter_var($email_address, FILTER_VALIDATE_EMAIL)) { - throw new \InvalidArgumentException('Email address is not a valid email format.'); - } - $subscribers = $this->get( 'subscribers', ['email_address' => $email_address] @@ -849,20 +987,6 @@ public function get_subscriber_id(string $email_address) return $subscribers->subscribers[0]->id; } - /** - * Get subscriber by id - * - * @param integer $subscriber_id Subscriber ID. - * - * @see https://developers.convertkit.com/#view-a-single-subscriber - * - * @return false|integer - */ - public function get_subscriber(int $subscriber_id) - { - return $this->get(sprintf('subscribers/%s', $subscriber_id)); - } - /** * Updates the information for a single subscriber. * @@ -871,7 +995,7 @@ public function get_subscriber(int $subscriber_id) * @param string $email_address New Email Address. * @param array $fields Updated Custom Fields. * - * @see https://developers.convertkit.com/#update-subscriber + * @see https://developers.convertkit.com/v4.html#update-a-subscriber * * @return false|mixed */ @@ -902,42 +1026,36 @@ public function update_subscriber( } /** - * Unsubscribe an email address from all forms and sequences. + * Unsubscribe an email address. * * @param string $email Email Address. * - * @see https://developers.convertkit.com/#unsubscribe-subscriber + * @see https://developers.convertkit.com/v4.html#unsubscribe-subscriber * * @return false|object */ public function unsubscribe(string $email) { - return $this->put( - 'unsubscribe', - ['email' => $email] + return $this->post( + sprintf( + 'subscribers/%s/unsubscribe', + $this->get_subscriber_id($email) + ) ); } /** - * Remove subscription from a form + * Unsubscribe the given subscriber ID. * - * @param array $options Array of user data (email). + * @param int $subscriber_id Subscriber ID. * - * @see https://developers.convertkit.com/#unsubscribe-subscriber + * @see https://developers.convertkit.com/v4.html#unsubscribe-subscriber * * @return false|object */ - public function form_unsubscribe(array $options) + public function unsubscribe_by_id(int $subscriber_id) { - // This function is deprecated in 1.0, as we prefer functions with structured arguments. - // This function name is also misleading, as it doesn't just unsubscribe the email - // address from forms. - trigger_error( - 'form_unsubscribe() is deprecated in 1.0. Use unsubscribe($email) instead.', - E_USER_NOTICE - ); - - return $this->put('unsubscribe', $options); + return $this->post(sprintf('subscribers/%s/unsubscribe', $subscriber_id)); } /** @@ -945,13 +1063,24 @@ public function form_unsubscribe(array $options) * * @param integer $subscriber_id Subscriber ID. * - * @see https://developers.convertkit.com/#list-tags-for-a-subscriber + * @see https://developers.convertkit.com/v4.html#list-tags-for-a-subscriber * * @return false|array */ - public function get_subscriber_tags(int $subscriber_id) - { - return $this->get(sprintf('subscribers/%s/tags', $subscriber_id)); + public function get_subscriber_tags( + int $subscriber_id, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + return $this->get( + endpoint: sprintf('subscribers/%s/tags', $subscriber_id), + args: $this->build_pagination_params( + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) + ); } /** From 344b717271b310f7cdaf7158fef5223101856deb Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 21 Mar 2024 15:04:35 +0000 Subject: [PATCH 04/78] Completed first pass of subscriber methods --- src/ConvertKit_API.php | 63 +++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 9ffedab..d831b6d 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -814,21 +814,21 @@ public function get_resources(string $resource) /** * Get subscribers. - * - * @since 2.0.0 * * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). * @param string $email_address Search susbcribers by email address. This is an exact match search. * @param \DateTime $created_after Filter subscribers who have been created after this date. * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. - * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. + * @param \DateTime $updated_after Filter subscribers who have been updated after this date. + * @param \DateTime $updated_before Filter subscribers who have been updated before this date. * @param string $sort_field Sort Field (id|updated_at|cancelled_at). * @param string $sort_order Sort Order (asc|desc). * @param string $after_cursor Return results after the given pagination cursor. * @param string $before_cursor Return results before the given pagination cursor. * @param integer $per_page Number of results to return. * + * @since 2.0.0 + * * @see https://developers.convertkit.com/v4.html#list-subscribers * * @return false|mixed @@ -845,8 +845,7 @@ public function get_subscribers( string $after_cursor = '', string $before_cursor = '', int $per_page = 100 - ) - { + ) { // Build parameters. $options = []; @@ -892,20 +891,20 @@ public function get_subscribers( /** * Create a subscriber. - * + * * Behaves as an upsert. If a subscriber with the provided email address does not exist, * it creates one with the specified first name and state. If a subscriber with the provided * email address already exists, it updates the first name. - * - * @since 2.0.0 - * - * @param string $email_address Email Address. - * @param string $first_name First Name. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param array $fields Custom Fields. - * + * + * @param string $email_address Email Address. + * @param string $first_name First Name. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param array $fields Custom Fields. + * + * @since 2.0.0 + * * @see https://developers.convertkit.com/v4.html#create-a-subscriber - * + * * @return mixed */ public function create_subscriber( @@ -913,16 +912,15 @@ public function create_subscriber( string $first_name = '', string $subscriber_state = '', array $fields = [] - ) - { + ) { // Build parameters. $options = []; if (!empty($first_name)) { $options['first_name'] = $first_name; } - if (!empty($state)) { - $options['state'] = $state; + if (!empty($subscriber_state)) { + $options['state'] = $subscriber_state; } if (count($fields)) { $options['fields'] = $fields; @@ -935,9 +933,27 @@ public function create_subscriber( ); } + /** + * Create multiple subscribers. + * + * @param array> $subscribers Subscribers. + * + * @since 2.0.0 + * + * @see https://developers.convertkit.com/v4.html#bulk-create-subscribers + * + * @return mixed + */ public function create_subscribers(array $subscribers) { - // @TODO. + // Build parameters. + $options = ['subscribers' => $subscribers]; + + // Send request. + return $this->post( + endpoint: 'bulk/subscribers', + args: $options + ); } /** @@ -1047,7 +1063,7 @@ public function unsubscribe(string $email) /** * Unsubscribe the given subscriber ID. * - * @param int $subscriber_id Subscriber ID. + * @param integer $subscriber_id Subscriber ID. * * @see https://developers.convertkit.com/v4.html#unsubscribe-subscriber * @@ -1062,6 +1078,9 @@ public function unsubscribe_by_id(int $subscriber_id) * Get a list of the tags for a subscriber. * * @param integer $subscriber_id Subscriber ID. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-tags-for-a-subscriber * From 2d9db50a9f849835294c7f49192baffdc76d45eb Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 21 Mar 2024 15:06:04 +0000 Subject: [PATCH 05/78] Added `callback_url` for bulk add subscribers --- src/ConvertKit_API.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index d831b6d..80c242d 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -936,7 +936,8 @@ public function create_subscriber( /** * Create multiple subscribers. * - * @param array> $subscribers Subscribers. + * @param array> $subscribers Subscribers. + * @param string $callback_url URL to notify for large batch size when async processing complete. * * @since 2.0.0 * @@ -944,11 +945,15 @@ public function create_subscriber( * * @return mixed */ - public function create_subscribers(array $subscribers) + public function create_subscribers(array $subscribers, string $callback_url = '') { // Build parameters. $options = ['subscribers' => $subscribers]; + if (!empty($callback_url)) { + $options['callback_url'] = $callback_url; + } + // Send request. return $this->post( endpoint: 'bulk/subscribers', From 24faea8164127356a4501987c7bede44ff5063df Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 21 Mar 2024 15:15:38 +0000 Subject: [PATCH 06/78] Started stubbing tests --- tests/ConvertKitAPITest.php | 167 ++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 9391fcd..4105a8c 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1592,6 +1592,173 @@ public function testAddSubscriberToFormWithTagID() $this->assertEquals($subscriberTags->tags[0]->id, $_ENV['CONVERTKIT_API_TAG_ID']); } + public function testGetSubscribers() + { + + } + + public function testGetSubscribersByEmailAddress() + { + + } + + public function testGetSubscribersWithBouncedSubscriberState() + { + + } + + public function testGetSubscribersWithAddedAfterParam() + { + + } + + public function testGetSubscribersWithAddedBeforeParam() + { + + } + + public function testGetSubscribersWithUpdatedAfterParam() + { + + } + + public function testGetSubscribersWithUpdatedBeforeParam() + { + + } + + public function testGetSubscibersWithSortFieldParam() + { + + } + + public function testGetSubscibersWithSortOrderParam() + { + + } + + public function testGetSubscribersPagination() + { + + } + + public function testGetSubscribersWithInvalidEmailAddress() + { + + } + + public function testGetSubscribersWithInvalidSubscriberState() + { + + } + + public function testGetSubscribersWithInvalidSortFieldParam() + { + + } + + public function testGetSubscribersWithInvalidSortOrderParam() + { + + } + + public function testGetSubscribersWithInvalidPagination() + { + + } + + /** + * Test that create_subscriber() returns the expected data. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreateSubscriber() + { + + } + + /** + * Test that create_subscriber() returns the expected data + * when a first name is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreateSubscriberWithFirstName() + { + + } + + /** + * Test that create_subscriber() returns the expected data + * when a subscriber state is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreateSubscriberWithSubscriberState() + { + + } + + /** + * Test that create_subscriber() returns the expected data + * when custom field data is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreateSubscriberWithCustomFields() + { + + } + + /** + * Test that create_subscriber() throws a ClientException when an invalid + * email address is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreateSubscriberWithInvalidEmailAddress() + { + + } + + /** + * Test that create_subscriber() throws a ClientException when an invalid + * subscriber state is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreateSubscriberWithInvalidSubscriberState() + { + + } + + public function testCreateSubscribers() + { + + } + + public function testCreateSubscribersWithBlankData() + { + + } + + public function testCreateSubscribersWithInvalidEmailAddresses() + { + + } + /** * Test that get_subscriber_id() returns the expected data. * From b0588cef94346211246845b9bda35787d71e058b Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 21 Mar 2024 15:40:26 +0000 Subject: [PATCH 07/78] Add `get_response_interface` and `get_response_status_code` helper methods --- src/ConvertKit_API.php | 48 +++++++++++++++++++++++++++++++++---- tests/ConvertKitAPITest.php | 39 ++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 3075af4..f3f215f 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -95,6 +95,20 @@ class ConvertKit_API */ protected $client; + /** + * Guzzle Http Response + * + * @var \Psr\Http\Message\ResponseInterface + */ + protected $response; + + /** + * Guzzle Http Status Code + * + * @var integer + */ + protected $status_code = 0; + /** * Constructor for ConvertKitAPI instance @@ -1636,21 +1650,45 @@ public function make_request(string $endpoint, string $method, array $args = []) } // Send request. - $response = $this->client->send( + $this->response = $this->client->send( $request, ['exceptions' => false] ); // Inspect response. - $status_code = $response->getStatusCode(); - $response_body = $response->getBody()->getContents(); + $this->status_code = $this->response->getStatusCode(); + $response_body = $this->response->getBody()->getContents(); // Log response. - $this->create_log(sprintf('Response Status Code: %s', $response->getStatusCode())); - $this->create_log(sprintf('Response Body: %s', $response->getBody()->getContents())); + $this->create_log(sprintf('Response Status Code: %s', $this->response->getStatusCode())); + $this->create_log(sprintf('Response Body: %s', $this->response->getBody()->getContents())); $this->create_log('Finish request successfully'); // Return response. return json_decode($response_body); } + + /** + * Returns the response interface used for the last API request. + * + * @since 2.0.0 + * + * @return \Psr\Http\Message\ResponseInterface + */ + public function get_response_interface() + { + return $this->response; + } + + /** + * Returns the HTTP status code for the last successful API request. + * + * @since 2.0.0 + * + * @return integer + */ + public function get_response_status_code() + { + return $this->status_code; + } } diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 9391fcd..fab0e7b 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -223,6 +223,45 @@ public function testDebugDisabled() $this->assertEmpty($this->getLogFileContents()); } + /** + * Test that a Response instance is returned when calling get_response_interface() + * after making an API request. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetResponseInterface() + { + // Assert response interface is null, as no API request made. + $this->assertNull($this->api->get_response_interface()); + + // Perform an API request. + $result = $this->api->get_account(); + + // Assert response interface is of a valid type. + $this->assertInstanceOf(Response::class, $this->api->get_response_interface()); + } + + /** + * Test that the get_response_status_code() returns the expected HTTP status code. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetResponseStatusCode() + { + // Assert no status code has yet been set in get_response_status_code, as no API request made. + $this->assertEquals(0, $this->api->get_response_status_code()); + + // Perform an API request. + $result = $this->api->get_account(); + + // Assert the correct status code defined in get_response_status_code() + $this->assertEquals(200, $this->api->get_response_status_code()); + } + /** * Test that get_oauth_url() returns the correct URL to begin the OAuth process. * From ba62560684d5e5e45364b0b4392b578dc33e3c11 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 21 Mar 2024 16:01:58 +0000 Subject: [PATCH 08/78] Remove `status_code`, as response object includes it --- README.md | 20 ++++++++++++++++++++ src/ConvertKit_API.php | 28 ++++------------------------ tests/ConvertKitAPITest.php | 26 +++++--------------------- 3 files changed, 29 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 7b2e6ae..4579b3d 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,26 @@ $api = new \ConvertKit_API\ConvertKit_API( ); ``` +API requests may then be performed: + +```php +$result = $api->add_subscriber_to_form(12345, 'joe.bloggs@convertkit.com'); +``` + +To determine whether a new entity / relationship was created, or an existing entity / relationship updated, inspect the HTTP code of the last request: + +```php +$result = $api->add_subscriber_to_form(12345, 'joe.bloggs@convertkit.com'); +$code = $api->getResponseInterface()->getStatusCode(); // 200 OK if e.g. a subscriber already added to the specified form, 201 Created if the subscriber added to the specified form for the first time. +``` + +The PSR-7 response can be fetched and further inspected, if required - for example, to check if a header exists: + +```php +$result = $api->add_subscriber_to_form(12345, 'joe.bloggs@convertkit.com'); +$api->getResponseInterface()->hasHeader('Content-Length'); // Check if the last API request included a `Content-Length` header +``` + ### 1.x (v3 API, API Key and Secret, PHP 7.4+) Get your ConvertKit API Key and API Secret [here](https://app.convertkit.com/account/edit) and set it somewhere in your application. diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index f3f215f..ef08595 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -102,13 +102,6 @@ class ConvertKit_API */ protected $response; - /** - * Guzzle Http Status Code - * - * @var integer - */ - protected $status_code = 0; - /** * Constructor for ConvertKitAPI instance @@ -1655,13 +1648,12 @@ public function make_request(string $endpoint, string $method, array $args = []) ['exceptions' => false] ); - // Inspect response. - $this->status_code = $this->response->getStatusCode(); - $response_body = $this->response->getBody()->getContents(); + // Get response. + $response_body = $this->response->getBody()->getContents(); // Log response. $this->create_log(sprintf('Response Status Code: %s', $this->response->getStatusCode())); - $this->create_log(sprintf('Response Body: %s', $this->response->getBody()->getContents())); + $this->create_log(sprintf('Response Body: %s', $response_body)); $this->create_log('Finish request successfully'); // Return response. @@ -1675,20 +1667,8 @@ public function make_request(string $endpoint, string $method, array $args = []) * * @return \Psr\Http\Message\ResponseInterface */ - public function get_response_interface() + public function getResponseInterface() { return $this->response; } - - /** - * Returns the HTTP status code for the last successful API request. - * - * @since 2.0.0 - * - * @return integer - */ - public function get_response_status_code() - { - return $this->status_code; - } } diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index fab0e7b..083aa36 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -224,7 +224,7 @@ public function testDebugDisabled() } /** - * Test that a Response instance is returned when calling get_response_interface() + * Test that a Response instance is returned when calling getResponseInterface() * after making an API request. * * @since 2.0.0 @@ -234,32 +234,16 @@ public function testDebugDisabled() public function testGetResponseInterface() { // Assert response interface is null, as no API request made. - $this->assertNull($this->api->get_response_interface()); + $this->assertNull($this->api->getResponseInterface()); // Perform an API request. $result = $this->api->get_account(); // Assert response interface is of a valid type. - $this->assertInstanceOf(Response::class, $this->api->get_response_interface()); - } - - /** - * Test that the get_response_status_code() returns the expected HTTP status code. - * - * @since 2.0.0 - * - * @return void - */ - public function testGetResponseStatusCode() - { - // Assert no status code has yet been set in get_response_status_code, as no API request made. - $this->assertEquals(0, $this->api->get_response_status_code()); - - // Perform an API request. - $result = $this->api->get_account(); + $this->assertInstanceOf(Response::class, $this->api->getResponseInterface()); - // Assert the correct status code defined in get_response_status_code() - $this->assertEquals(200, $this->api->get_response_status_code()); + // Assert the correct status code was returned. + $this->assertEquals(200, $this->api->getResponseInterface()->getStatusCode()); } /** From f116beb4de0494947f5a8cd5234a7b47e636abaa Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 21 Mar 2024 16:08:49 +0000 Subject: [PATCH 09/78] Moved test --- tests/ConvertKitAPITest.php | 52 +++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 083aa36..4cf6f6b 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -61,6 +61,29 @@ protected function setUp(): void ); } + /** + * Test that a Response instance is returned when calling getResponseInterface() + * after making an API request. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetResponseInterface() + { + // Assert response interface is null, as no API request made. + $this->assertNull($this->api->getResponseInterface()); + + // Perform an API request. + $result = $this->api->get_account(); + + // Assert response interface is of a valid type. + $this->assertInstanceOf(Response::class, $this->api->getResponseInterface()); + + // Assert the correct status code was returned. + $this->assertEquals(200, $this->api->getResponseInterface()->getStatusCode()); + } + /** * Test that a ClientInterface can be injected. * @@ -95,6 +118,12 @@ public function testClientInterfaceInjection() $this->assertSame('Test Account for Guzzle Mock', $result->name); $this->assertSame('free', $result->plan_type); $this->assertSame('mock@guzzle.mock', $result->primary_email_address); + + // Assert response interface is of a valid type when using `set_http_client`. + $this->assertInstanceOf(Response::class, $this->api->getResponseInterface()); + + // Assert the correct status code was returned. + $this->assertEquals(200, $this->api->getResponseInterface()->getStatusCode()); } /** @@ -223,29 +252,6 @@ public function testDebugDisabled() $this->assertEmpty($this->getLogFileContents()); } - /** - * Test that a Response instance is returned when calling getResponseInterface() - * after making an API request. - * - * @since 2.0.0 - * - * @return void - */ - public function testGetResponseInterface() - { - // Assert response interface is null, as no API request made. - $this->assertNull($this->api->getResponseInterface()); - - // Perform an API request. - $result = $this->api->get_account(); - - // Assert response interface is of a valid type. - $this->assertInstanceOf(Response::class, $this->api->getResponseInterface()); - - // Assert the correct status code was returned. - $this->assertEquals(200, $this->api->getResponseInterface()->getStatusCode()); - } - /** * Test that get_oauth_url() returns the correct URL to begin the OAuth process. * From 58c96ead03226956cd08cfb17cf03b6066533966 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 22 Mar 2024 14:52:33 +0000 Subject: [PATCH 10/78] Started writing tests for `get_subscribers` --- src/ConvertKit_API.php | 28 ++++---- tests/ConvertKitAPITest.php | 140 ++++++++++++++++++++++++++++++++++-- 2 files changed, 150 insertions(+), 18 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 7735fa3..738b49a 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1075,20 +1075,6 @@ public function create_subscribers(array $subscribers, string $callback_url = '' ); } - /** - * Get subscriber by id - * - * @param integer $subscriber_id Subscriber ID. - * - * @see https://developers.convertkit.com/v4.html#get-a-subscriber - * - * @return false|integer - */ - public function get_subscriber(int $subscriber_id) - { - return $this->get(sprintf('subscribers/%s', $subscriber_id)); - } - /** * Get the ConvertKit subscriber ID associated with email address if it exists. * Return false if subscriber not found. @@ -1122,6 +1108,20 @@ public function get_subscriber_id(string $email_address) return $subscribers->subscribers[0]->id; } + /** + * Get subscriber by id + * + * @param integer $subscriber_id Subscriber ID. + * + * @see https://developers.convertkit.com/v4.html#get-a-subscriber + * + * @return false|integer + */ + public function get_subscriber(int $subscriber_id) + { + return $this->get(sprintf('subscribers/%s', $subscriber_id)); + } + /** * Updates the information for a single subscriber. * diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index af1e581..d7a82bd 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -2049,44 +2049,176 @@ public function testAddSubscriberToFormByIDWithInvalidSubscriberID() ); } + /** + * Test that get_subscribers() returns the expected data. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribers() { + $result = $this->api->get_subscribers(); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); } + /** + * Test that get_subscribers() returns the expected data when + * searching by an email address. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersByEmailAddress() { - + $result = $this->api->get_subscribers( + email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert correct subscriber returned. + $this->assertEquals( + get_object_vars($result->subscriber)['email_address'], + $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + ); } + /** + * Test that get_subscribers() returns the expected data + * when the subscription status is bounced. + * + * @since 1.0.0 + * + * @return void + */ public function testGetSubscribersWithBouncedSubscriberState() { + $result = $this->api->get_subscribers( + subscriber_state: 'bounced' + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + // Check the correct subscribers were returned. + $this->assertEquals($result->subscribers[0]->state, 'bounced'); } + /** + * Test that get_subscribers() returns the expected data + * when the added_after parameter is used. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersWithAddedAfterParam() { + $date = new \DateTime('2024-01-01'); + $result = $this->api->get_subscribers( + added_after: $date + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + // Check the correct subscribers were returned. + $this->assertGreaterThanOrEqual( + $date->format('Y-m-d'), + date('Y-m-d', strtotime($result->subscribers[0]->added_at)) + ); } + /** + * Test that get_subscribers() returns the expected data + * when the added_before parameter is used. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersWithAddedBeforeParam() { - + $date = new \DateTime('2024-01-01'); + $result = $this->api->get_subscribers( + added_before: $date + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Check the correct subscribers were returned. + $this->assertLessThanOrEqual( + $date->format('Y-m-d'), + date('Y-m-d', strtotime($result->subscribers[0]->added_at)) + ); } + /** + * Test that get_subscribers() returns the expected data + * when the updated_after parameter is used. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersWithUpdatedAfterParam() { + $date = new \DateTime('2024-01-01'); + $result = $this->api->get_form_subscriptions( + updated_after: $date + ); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Check the correct subscribers were returned. + $this->assertGreaterThanOrEqual( + $date->format('Y-m-d'), + date('Y-m-d', strtotime($result->subscribers[0]->created_at)) + ); } + /** + * Test that get_subscribers() returns the expected data + * when the updated_before parameter is used. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersWithUpdatedBeforeParam() { - + $date = new \DateTime('2024-01-01'); + $result = $this->api->get_form_subscriptions( + updated_before: $date + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Check the correct subscribers were returned. + $this->assertGreaterThanOrEqual( + $date->format('Y-m-d'), + date('Y-m-d', strtotime($result->subscribers[0]->created_at)) + ); } public function testGetSubscibersWithSortFieldParam() { - + } public function testGetSubscibersWithSortOrderParam() From 134f6618ac4c44b4a9c4092d73f8eb747e468d40 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 22 Mar 2024 15:06:15 +0000 Subject: [PATCH 11/78] Continued tests for `get_subscribers` --- tests/ConvertKitAPITest.php | 135 +++++++++++++++++++++++++++++------- 1 file changed, 109 insertions(+), 26 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index d7a82bd..eaa72f9 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -2114,17 +2114,17 @@ public function testGetSubscribersWithBouncedSubscriberState() /** * Test that get_subscribers() returns the expected data - * when the added_after parameter is used. + * when the created_after parameter is used. * * @since 2.0.0 * * @return void */ - public function testGetSubscribersWithAddedAfterParam() + public function testGetSubscribersWithCreatedAfterParam() { $date = new \DateTime('2024-01-01'); $result = $this->api->get_subscribers( - added_after: $date + created_after: $date ); // Assert subscribers and pagination exist. @@ -2134,23 +2134,23 @@ public function testGetSubscribersWithAddedAfterParam() // Check the correct subscribers were returned. $this->assertGreaterThanOrEqual( $date->format('Y-m-d'), - date('Y-m-d', strtotime($result->subscribers[0]->added_at)) + date('Y-m-d', strtotime($result->subscribers[0]->created_at)) ); } /** * Test that get_subscribers() returns the expected data - * when the added_before parameter is used. + * when the created_before parameter is used. * * @since 2.0.0 * * @return void */ - public function testGetSubscribersWithAddedBeforeParam() + public function testGetSubscribersWithCreatedBeforeParam() { $date = new \DateTime('2024-01-01'); $result = $this->api->get_subscribers( - added_before: $date + created_before: $date ); // Assert subscribers and pagination exist. @@ -2160,7 +2160,7 @@ public function testGetSubscribersWithAddedBeforeParam() // Check the correct subscribers were returned. $this->assertLessThanOrEqual( $date->format('Y-m-d'), - date('Y-m-d', strtotime($result->subscribers[0]->added_at)) + date('Y-m-d', strtotime($result->subscribers[0]->created_at)) ); } @@ -2175,19 +2175,13 @@ public function testGetSubscribersWithAddedBeforeParam() public function testGetSubscribersWithUpdatedAfterParam() { $date = new \DateTime('2024-01-01'); - $result = $this->api->get_form_subscriptions( + $result = $this->api->get_subscribers( updated_after: $date ); // Assert subscribers and pagination exist. $this->assertDataExists($result, 'subscribers'); $this->assertPaginationExists($result); - - // Check the correct subscribers were returned. - $this->assertGreaterThanOrEqual( - $date->format('Y-m-d'), - date('Y-m-d', strtotime($result->subscribers[0]->created_at)) - ); } /** @@ -2201,34 +2195,123 @@ public function testGetSubscribersWithUpdatedAfterParam() public function testGetSubscribersWithUpdatedBeforeParam() { $date = new \DateTime('2024-01-01'); - $result = $this->api->get_form_subscriptions( + $result = $this->api->get_subscribers( updated_before: $date ); // Assert subscribers and pagination exist. $this->assertDataExists($result, 'subscribers'); $this->assertPaginationExists($result); - - // Check the correct subscribers were returned. - $this->assertGreaterThanOrEqual( - $date->format('Y-m-d'), - date('Y-m-d', strtotime($result->subscribers[0]->created_at)) - ); } - public function testGetSubscibersWithSortFieldParam() + /** + * Test that get_subscribers() returns the expected data + * when the sort_field parameter is used. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSubscribersWithSortFieldParam() { - + $result = $this->api->get_subscribers( + sort_field: 'id' + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert sorting is honored by ID in descending (default) order. + $this->assertLessThanOrEqual( + $result->subscribers[0]->id, + $result->subscribers[1]->id + ); } - public function testGetSubscibersWithSortOrderParam() + /** + * Test that get_subscribers() returns the expected data + * when the sort_order parameter is used. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSubscribersWithSortOrderParam() { - + $result = $this->api->get_subscribers( + sort_order: 'asc' + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert sorting is honored by ID (default) in ascending order. + $this->assertGreaterThanOrEqual( + $result->subscribers[0]->id, + $result->subscribers[1]->id + ); } + /** + * Test that get_subscribers() returns the expected data + * when pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersPagination() { + $result = $this->api->get_subscribers( + per_page: 1 + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert a single subscriber was returned. + $this->assertCount(1, $result->subscribers); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->get_subscribers( + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert a single subscriber was returned. + $this->assertCount(1, $result->subscribers); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_subscribers( + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert a single subscriber was returned. + $this->assertCount(1, $result->subscribers); + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); } public function testGetSubscribersWithInvalidEmailAddress() From f9e515d291b4dab19053640cc1f9613b8a62a6b8 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 22 Mar 2024 15:10:38 +0000 Subject: [PATCH 12/78] Completed `get_subscribers` tests --- tests/ConvertKitAPITest.php | 92 +++++++++++++++++++++++++++---------- 1 file changed, 69 insertions(+), 23 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index eaa72f9..fb41d20 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -2085,7 +2085,7 @@ public function testGetSubscribersByEmailAddress() // Assert correct subscriber returned. $this->assertEquals( - get_object_vars($result->subscriber)['email_address'], + $result->subscribers[0]->email_address, $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); } @@ -2314,80 +2314,131 @@ public function testGetSubscribersPagination() $this->assertTrue($result->pagination->has_next_page); } + /** + * Test that get_subscribers() throws a ClientException when an invalid + * email address is specified. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersWithInvalidEmailAddress() { - + $this->expectException(ClientException::class); + $result = $this->api->get_subscribers( + email_address: 'not-an-email-address' + ); } + /** + * Test that get_subscribers() throws a ClientException when an invalid + * subscriber state is specified. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersWithInvalidSubscriberState() { - + $this->expectException(ClientException::class); + $result = $this->api->get_subscribers( + subscriber_state: 'not-an-valid-state' + ); } + /** + * Test that get_subscribers() throws a ClientException when an invalid + * sort field is specified. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersWithInvalidSortFieldParam() { - + $this->expectException(ClientException::class); + $result = $this->api->get_subscribers( + sort_field: 'not-a-valid-sort-field' + ); } + /** + * Test that get_subscribers() throws a ClientException when an invalid + * sort order is specified. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersWithInvalidSortOrderParam() { - + $this->expectException(ClientException::class); + $result = $this->api->get_subscribers( + sort_order: 'not-a-valid-sort-order' + ); } + /** + * Test that get_subscribers() throws a ClientException when an invalid + * pagination parameters are specified. + * + * @since 2.0.0 + * + * @return void + */ public function testGetSubscribersWithInvalidPagination() { - + $this->expectException(ClientException::class); + $result = $this->api->get_subscribers( + after_cursor: 'not-a-valid-cursor' + ); } /** * Test that create_subscriber() returns the expected data. - * + * * @since 2.0.0 - * + * * @return void */ public function testCreateSubscriber() { - } /** * Test that create_subscriber() returns the expected data * when a first name is included. - * + * * @since 2.0.0 - * + * * @return void */ public function testCreateSubscriberWithFirstName() { - } /** * Test that create_subscriber() returns the expected data * when a subscriber state is included. - * + * * @since 2.0.0 - * + * * @return void */ public function testCreateSubscriberWithSubscriberState() { - } /** * Test that create_subscriber() returns the expected data * when custom field data is included. - * + * * @since 2.0.0 - * + * * @return void */ public function testCreateSubscriberWithCustomFields() { - } /** @@ -2400,7 +2451,6 @@ public function testCreateSubscriberWithCustomFields() */ public function testCreateSubscriberWithInvalidEmailAddress() { - } /** @@ -2413,22 +2463,18 @@ public function testCreateSubscriberWithInvalidEmailAddress() */ public function testCreateSubscriberWithInvalidSubscriberState() { - } public function testCreateSubscribers() { - } public function testCreateSubscribersWithBlankData() { - } public function testCreateSubscribersWithInvalidEmailAddresses() { - } /** From 43a23758c9682e8721d7603cc151d156479cb8d5 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 22 Mar 2024 15:58:15 +0000 Subject: [PATCH 13/78] Started tests for `create_subscriber` --- src/ConvertKit_API.php | 4 +- tests/ConvertKitAPITest.php | 81 +++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 738b49a..9090fee 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1028,7 +1028,9 @@ public function create_subscriber( array $fields = [] ) { // Build parameters. - $options = []; + $options = [ + 'email_address' => $email_address, + ]; if (!empty($first_name)) { $options['first_name'] = $first_name; diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index fb41d20..f480247 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -2403,6 +2403,16 @@ public function testGetSubscribersWithInvalidPagination() */ public function testCreateSubscriber() { + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress + ); + + // Assert subscriber exists with correct data. + $this->assertEquals($result->subscriber->email_address, $emailAddress); + + // Unsubscribe to cleanup test. + $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2415,6 +2425,19 @@ public function testCreateSubscriber() */ public function testCreateSubscriberWithFirstName() { + $firstName = 'FirstName'; + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress, + first_name: $firstName + ); + + // Assert subscriber exists with correct data. + $this->assertEquals($result->subscriber->email_address, $emailAddress); + $this->assertEquals($result->subscriber->first_name, $firstName); + + // Unsubscribe to cleanup test. + $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2427,6 +2450,19 @@ public function testCreateSubscriberWithFirstName() */ public function testCreateSubscriberWithSubscriberState() { + $subscriberState = 'inactive'; + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress, + subscriber_state: $subscriberState + ); + + // Assert subscriber exists with correct data. + $this->assertEquals($result->subscriber->email_address, $emailAddress); + $this->assertEquals($result->subscriber->state, $subscriberState); + + // Unsubscribe to cleanup test. + $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2439,6 +2475,21 @@ public function testCreateSubscriberWithSubscriberState() */ public function testCreateSubscriberWithCustomFields() { + $lastName = 'LastName'; + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress, + fields: [ + 'last_name' => $lastName + ] + ); + + // Assert subscriber exists with correct data. + $this->assertEquals($result->subscriber->email_address, $emailAddress); + $this->assertEquals($result->subscriber->fields->last_name, $lastName); + + // Unsubscribe to cleanup test. + $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2451,6 +2502,10 @@ public function testCreateSubscriberWithCustomFields() */ public function testCreateSubscriberWithInvalidEmailAddress() { + $this->expectException(ClientException::class); + $result = $this->api->create_subscriber( + email_address: 'not-an-email-address' + ); } /** @@ -2463,6 +2518,32 @@ public function testCreateSubscriberWithInvalidEmailAddress() */ public function testCreateSubscriberWithInvalidSubscriberState() { + $this->expectException(ClientException::class); + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress, + subscriber_state: 'not-a-valid-state' + ); + } + + /** + * Test that create_subscriber() throws a ClientException when an invalid + * custom field is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreateSubscriberWithInvalidCustomFields() + { + + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress, + fields: [ + 'not_a_custom_field' => 'value' + ] + ); } public function testCreateSubscribers() From c61d362ab9b1692d9e3d9caf2f67c0b3f2279dc6 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 22 Mar 2024 16:50:04 +0000 Subject: [PATCH 14/78] Completed `create_subscriber` tests --- src/ConvertKit_API.php | 4 +- tests/ConvertKitAPITest.php | 75 ++++++++++++++++++++++++++++++++++--- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 9090fee..863752b 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1028,9 +1028,7 @@ public function create_subscriber( array $fields = [] ) { // Build parameters. - $options = [ - 'email_address' => $email_address, - ]; + $options = ['email_address' => $email_address]; if (!empty($first_name)) { $options['first_name'] = $first_name; diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index f480247..22eeaad 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -2435,7 +2435,7 @@ public function testCreateSubscriberWithFirstName() // Assert subscriber exists with correct data. $this->assertEquals($result->subscriber->email_address, $emailAddress); $this->assertEquals($result->subscriber->first_name, $firstName); - + // Unsubscribe to cleanup test. $this->api->unsubscribe_by_id($result->subscriber->id); } @@ -2460,7 +2460,7 @@ public function testCreateSubscriberWithSubscriberState() // Assert subscriber exists with correct data. $this->assertEquals($result->subscriber->email_address, $emailAddress); $this->assertEquals($result->subscriber->state, $subscriberState); - + // Unsubscribe to cleanup test. $this->api->unsubscribe_by_id($result->subscriber->id); } @@ -2487,7 +2487,7 @@ public function testCreateSubscriberWithCustomFields() // Assert subscriber exists with correct data. $this->assertEquals($result->subscriber->email_address, $emailAddress); $this->assertEquals($result->subscriber->fields->last_name, $lastName); - + // Unsubscribe to cleanup test. $this->api->unsubscribe_by_id($result->subscriber->id); } @@ -2527,8 +2527,8 @@ public function testCreateSubscriberWithInvalidSubscriberState() } /** - * Test that create_subscriber() throws a ClientException when an invalid - * custom field is specified. + * Test that create_subscriber() returns the expected data + * when an invalid custom field is included. * * @since 2.0.0 * @@ -2544,18 +2544,83 @@ public function testCreateSubscriberWithInvalidCustomFields() 'not_a_custom_field' => 'value' ] ); + + // Assert subscriber exists with correct data. + $this->assertEquals($result->subscriber->email_address, $emailAddress); + + // Unsubscribe to cleanup test. + $this->api->unsubscribe_by_id($result->subscriber->id); } + /** + * Test that create_subscribers() returns the expected data. + * + * @since 2.0.0 + * + * @return void + */ public function testCreateSubscribers() { + $subscribers = [ + [ + 'email_address' => str_replace('@convertkit.com', '-1@convertkit.com', $this->generateEmailAddress()), + ], + [ + 'email_address' => str_replace('@convertkit.com', '-2@convertkit.com', $this->generateEmailAddress()), + ], + ]; + $result = $this->api->create_subscribers($subscribers); + + // Assert no failures. + $this->assertCount(0, $result->failures); + + // Assert subscribers exists with correct data. + foreach ($result->subscribers as $i => $subscriber) { + $this->assertEquals($subscriber->email_address, $subscribers[$i]['email_address']); + + // Unsubscribe to cleanup test. + $this->api->unsubscribe_by_id($subscriber->id); + } } + /** + * Test that create_subscribers() throws a ClientException when no data is specified. + * + * @since 2.0.0 + * + * @return void + */ public function testCreateSubscribersWithBlankData() { + $this->expectException(ClientException::class); + $result = $this->api->create_subscribers([ + [], + ]); } + /** + * Test that create_subscribers() returns the expected data when invalid email addresses + * are specified. + * + * @since 2.0.0 + * + * @return void + */ public function testCreateSubscribersWithInvalidEmailAddresses() { + $subscribers = [ + [ + 'email_address' => 'not-an-email-address', + ], + [ + 'email_address' => 'not-an-email-address-again', + ], + ]; + $result = $this->api->create_subscribers($subscribers); + + // Assert no subscribers were added. + $this->assertCount(0, $result->subscribers); + $this->assertCount(2, $result->failures); } /** From d657bfa088fda305ce08572db4d5b0612e2ac777 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 22 Mar 2024 17:26:58 +0000 Subject: [PATCH 15/78] Completed tests --- src/ConvertKit_API.php | 7 +- tests/ConvertKitAPITest.php | 252 ++++++++++++++++++++++-------------- 2 files changed, 156 insertions(+), 103 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 863752b..35a1557 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1094,12 +1094,7 @@ public function get_subscriber_id(string $email_address) ['email_address' => $email_address] ); - if (!$subscribers) { - $this->create_log('No subscribers'); - return false; - } - - if ($subscribers->total_subscribers === 0) { + if (!count($subscribers->subscribers)) { $this->create_log('No subscribers'); return false; } diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 22eeaad..39037a2 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -2632,8 +2632,6 @@ public function testCreateSubscribersWithInvalidEmailAddresses() */ public function testGetSubscriberID() { - $this->markTestIncomplete(); - $subscriber_id = $this->api->get_subscriber_id($_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']); $this->assertIsInt($subscriber_id); $this->assertEquals($subscriber_id, (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); @@ -2649,9 +2647,7 @@ public function testGetSubscriberID() */ public function testGetSubscriberIDWithInvalidEmailAddress() { - $this->markTestIncomplete(); - - $this->expectException(InvalidArgumentException::class); + $this->expectException(ClientException::class); $result = $this->api->get_subscriber_id('not-an-email-address'); } @@ -2665,8 +2661,6 @@ public function testGetSubscriberIDWithInvalidEmailAddress() */ public function testGetSubscriberIDWithNotSubscribedEmailAddress() { - $this->markTestIncomplete(); - $result = $this->api->get_subscriber_id('not-a-subscriber@test.com'); $this->assertFalse($result); } @@ -2680,16 +2674,11 @@ public function testGetSubscriberIDWithNotSubscribedEmailAddress() */ public function testGetSubscriber() { - $this->markTestIncomplete(); + $result = $this->api->get_subscriber((int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); - $subscriber = $this->api->get_subscriber((int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); - $this->assertInstanceOf('stdClass', $subscriber); - $this->assertArrayHasKey('subscriber', get_object_vars($subscriber)); - $this->assertArrayHasKey('id', get_object_vars($subscriber->subscriber)); - $this->assertEquals( - get_object_vars($subscriber->subscriber)['id'], - (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] - ); + // Assert subscriber exists with correct data. + $this->assertEquals($result->subscriber->id, $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); + $this->assertEquals($result->subscriber->email_address, $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']); } /** @@ -2702,8 +2691,6 @@ public function testGetSubscriber() */ public function testGetSubscriberWithInvalidSubscriberID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $subscriber = $this->api->get_subscriber(12345); } @@ -2717,13 +2704,11 @@ public function testGetSubscriberWithInvalidSubscriberID() */ public function testUpdateSubscriberWithNoChanges() { - $this->markTestIncomplete(); - $result = $this->api->update_subscriber($_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); - $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscriber', get_object_vars($result)); - $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); - $this->assertEquals(get_object_vars($result->subscriber)['id'], $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); + + // Assert subscriber exists with correct data. + $this->assertEquals($result->subscriber->id, $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); + $this->assertEquals($result->subscriber->email_address, $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL']); } /** @@ -2735,33 +2720,31 @@ public function testUpdateSubscriberWithNoChanges() */ public function testUpdateSubscriberFirstName() { - $this->markTestIncomplete(); - // Add a subscriber. - $email = $this->generateEmailAddress(); - $result = $this->api->add_subscriber_to_sequence( - sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - email: $email + $firstName = 'FirstName'; + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress ); + // Assert subscriber created with no first name. + $this->assertNull($result->subscriber->first_name); + // Get subscriber ID. - $subscriberID = $result->subscription->subscriber->id; + $subscriberID = $result->subscriber->id; // Update subscriber's first name. $result = $this->api->update_subscriber( subscriber_id: $subscriberID, - first_name: 'First Name' + first_name: $firstName ); - // Confirm the change is reflected in the subscriber. - $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscriber', get_object_vars($result)); - $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); - $this->assertEquals(get_object_vars($result->subscriber)['id'], $subscriberID); - $this->assertEquals(get_object_vars($result->subscriber)['first_name'], 'First Name'); + // Assert changes were made. + $this->assertEquals($result->subscriber->id, $subscriberID); + $this->assertEquals($result->subscriber->first_name, $firstName); - // Unsubscribe. - $this->api->unsubscribe($email); + // Unsubscribe to cleanup test. + $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2773,17 +2756,17 @@ public function testUpdateSubscriberFirstName() */ public function testUpdateSubscriberEmailAddress() { - $this->markTestIncomplete(); - // Add a subscriber. - $email = $this->generateEmailAddress(); - $result = $this->api->add_subscriber_to_sequence( - sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - email: $email + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress ); + // Assert subscriber created. + $this->assertEquals($result->subscriber->email_address, $emailAddress); + // Get subscriber ID. - $subscriberID = $result->subscription->subscriber->id; + $subscriberID = $result->subscriber->id; // Update subscriber's email address. $newEmail = $this->generateEmailAddress(); @@ -2792,15 +2775,12 @@ public function testUpdateSubscriberEmailAddress() email_address: $newEmail ); - // Confirm the change is reflected in the subscriber. - $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscriber', get_object_vars($result)); - $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); - $this->assertEquals(get_object_vars($result->subscriber)['id'], $subscriberID); - $this->assertEquals(get_object_vars($result->subscriber)['email_address'], $newEmail); + // Assert changes were made. + $this->assertEquals($result->subscriber->id, $subscriberID); + $this->assertEquals($result->subscriber->email_address, $newEmail); - // Unsubscribe. - $this->api->unsubscribe($newEmail); + // Unsubscribe to cleanup test. + $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2812,35 +2792,33 @@ public function testUpdateSubscriberEmailAddress() */ public function testUpdateSubscriberCustomFields() { - $this->markTestIncomplete(); - // Add a subscriber. - $email = $this->generateEmailAddress(); - $result = $this->api->add_subscriber_to_sequence( - sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - email: $email + $lastName = 'LastName'; + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress ); + // Assert subscriber created. + $this->assertEquals($result->subscriber->email_address, $emailAddress); + // Get subscriber ID. - $subscriberID = $result->subscription->subscriber->id; + $subscriberID = $result->subscriber->id; - // Update subscriber's email address. + // Update subscriber's custom fields. $result = $this->api->update_subscriber( subscriber_id: $subscriberID, fields: [ - 'last_name' => 'Last Name', + 'last_name' => $lastName, ] ); - // Confirm the change is reflected in the subscriber. - $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscriber', get_object_vars($result)); - $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); - $this->assertEquals(get_object_vars($result->subscriber)['id'], $subscriberID); - $this->assertEquals($result->subscriber->fields->last_name, 'Last Name'); + // Assert changes were made. + $this->assertEquals($result->subscriber->id, $subscriberID); + $this->assertEquals($result->subscriber->fields->last_name, $lastName); - // Unsubscribe. - $this->api->unsubscribe($email); + // Unsubscribe to cleanup test. + $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2853,8 +2831,6 @@ public function testUpdateSubscriberCustomFields() */ public function testUpdateSubscriberWithInvalidSubscriberID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $subscriber = $this->api->update_subscriber(12345); } @@ -2868,23 +2844,14 @@ public function testUpdateSubscriberWithInvalidSubscriberID() */ public function testUnsubscribe() { - $this->markTestIncomplete(); - // Add a subscriber. - $email = $this->generateEmailAddress(); - $result = $this->api->add_subscriber_to_sequence( - sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - email: $email + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress ); // Unsubscribe. - $result = $this->api->unsubscribe($email); - - // Confirm the change is reflected in the subscriber. - $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscriber', get_object_vars($result)); - $this->assertEquals($result->subscriber->email_address, $email); - $this->assertEquals($result->subscriber->state, 'cancelled'); + $this->assertNull($this->api->unsubscribe($emailAddress)); } /** @@ -2897,8 +2864,6 @@ public function testUnsubscribe() */ public function testUnsubscribeWithNotSubscribedEmailAddress() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $subscriber = $this->api->unsubscribe('not-subscribed@convertkit.com'); } @@ -2913,12 +2878,43 @@ public function testUnsubscribeWithNotSubscribedEmailAddress() */ public function testUnsubscribeWithInvalidEmailAddress() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $subscriber = $this->api->unsubscribe('invalid-email'); } + /** + * Test that unsubscribe() works with a valid subscriber ID. + * + * @since 2.0.0 + * + * @return void + */ + public function testUnsubscribeByID() + { + // Add a subscriber. + $emailAddress = $this->generateEmailAddress(); + $result = $this->api->create_subscriber( + email_address: $emailAddress + ); + + // Unsubscribe. + $this->assertNull($this->api->unsubscribe_by_id($result->subscriber->id)); + } + + /** + * Test that unsubscribe() throws a ClientException when an invalid + * subscriber ID is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testUnsubscribeByIDWithInvalidSubscriberID() + { + $this->expectException(ClientException::class); + $subscriber = $this->api->unsubscribe_by_id(12345); + } + /** * Test that get_subscriber_tags() returns the expected data. * @@ -2928,11 +2924,11 @@ public function testUnsubscribeWithInvalidEmailAddress() */ public function testGetSubscriberTags() { - $this->markTestIncomplete(); + $result = $this->api->get_subscriber_tags((int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); - $subscriber = $this->api->get_subscriber_tags((int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); - $this->assertInstanceOf('stdClass', $subscriber); - $this->assertArrayHasKey('tags', get_object_vars($subscriber)); + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); } /** @@ -2945,12 +2941,74 @@ public function testGetSubscriberTags() */ public function testGetSubscriberTagsWithInvalidSubscriberID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $subscriber = $this->api->get_subscriber_tags(12345); } + /** + * Test that get_subscriber_tags() returns the expected data + * when a valid Subscriber ID is specified and pagination parameters + * and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSubscriberTagsPagination() + { + $result = $this->api->get_subscriber_tags( + subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], + per_page: 1 + ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert a single tag was returned. + $this->assertCount(1, $result->tags); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->get_subscriber_tags( + subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert a single tag was returned. + $this->assertCount(1, $result->tags); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_subscriber_tags( + subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert a single tag was returned. + $this->assertCount(1, $result->tags); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + } + /** * Test that create_broadcast(), update_broadcast() and destroy_broadcast() works * when specifying valid published_at and send_at values. From 099a8b473909f6fd8af9c50229e3afdbe613d762 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 Mar 2024 14:22:26 +0000 Subject: [PATCH 16/78] testCreateSubscriberWithSubscriberState: Use `cancelled` state for test --- tests/ConvertKitAPITest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 39037a2..83e4be9 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -2450,7 +2450,7 @@ public function testCreateSubscriberWithFirstName() */ public function testCreateSubscriberWithSubscriberState() { - $subscriberState = 'inactive'; + $subscriberState = 'cancelled'; $emailAddress = $this->generateEmailAddress(); $result = $this->api->create_subscriber( email_address: $emailAddress, From 026089652bc6a482e41f485b1065dcbb3b9b78f8 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 Mar 2024 15:42:27 +0000 Subject: [PATCH 17/78] Tests: Added tag subscriber tests --- tests/ConvertKitAPITest.php | 133 ++++++++++++++++++++++++++++++++---- 1 file changed, 119 insertions(+), 14 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 7f2371c..45b05fe 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1450,6 +1450,66 @@ public function testGetTags() $this->assertArrayHasKey('created_at', $tag); } + /** + * Test that get_tags() returns the expected data + * when pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetTagsPagination() + { + $result = $this->api->get_tags( + per_page: 1 + ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert a single tag was returned. + $this->assertCount(1, $result->tags); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->get_tags( + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert a single subscriber was returned. + $this->assertCount(1, $result->tags); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_tags( + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert a single subscriber was returned. + $this->assertCount(1, $result->tags); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + } + /** * Test that create_tag() returns the expected data. * @@ -1567,17 +1627,32 @@ public function testCreateTagsThatExist() */ public function testTagSubscriber() { - $result = $this->api->tag_subscriber( + // Create subscriber. + $emailAddress = $this->generateEmailAddress(); + $this->api->create_subscriber( + email_address: $emailAddress + ); + + // Tag subscriber by email. + $subscriber = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + email: $emailAddress, ); - $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscriber', get_object_vars($result)); - $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); - $this->assertEquals( - get_object_vars($result->subscriber)['email_address'], - $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + $this->assertArrayHasKey('subscriber', get_object_vars($subscriber)); + $this->assertArrayHasKey('id', get_object_vars($subscriber->subscriber)); + $this->assertArrayHasKey('tagged_at', get_object_vars($subscriber->subscriber)); + + // Confirm the subscriber is tagged. + $result = $this->api->get_subscriber_tags( + subscriber_id: $subscriber->subscriber->id ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert correct tag was assigned. + $this->assertEquals($result->tags[0]->id, $_ENV['CONVERTKIT_API_TAG_ID']); } /** @@ -1590,10 +1665,16 @@ public function testTagSubscriber() */ public function testTagSubscriberWithInvalidTagID() { + // Create subscriber. + $emailAddress = $this->generateEmailAddress(); + $this->api->create_subscriber( + email_address: $emailAddress + ); + $this->expectException(ClientException::class); $result = $this->api->tag_subscriber( tag_id: 12345, - email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + email: $emailAddress ); } @@ -1623,14 +1704,32 @@ public function testTagSubscriberWithInvalidEmailAddress() */ public function testTagSubscriberByID() { + // Create subscriber. + $emailAddress = $this->generateEmailAddress(); + $subscriber = $this->api->create_subscriber( + email_address: $emailAddress + ); + + // Tag subscriber by email. $result = $this->api->tag_subscriber_by_subscriber_id( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] + subscriber_id: $subscriber->subscriber->id, ); - $this->assertInstanceOf('stdClass', $result); $this->assertArrayHasKey('subscriber', get_object_vars($result)); $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); - $this->assertEquals(get_object_vars($result->subscriber)['id'], $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); + $this->assertArrayHasKey('tagged_at', get_object_vars($result->subscriber)); + + // Confirm the subscriber is tagged. + $result = $this->api->get_subscriber_tags( + subscriber_id: $result->subscriber->id + ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert correct tag was assigned. + $this->assertEquals($result->tags[0]->id, $_ENV['CONVERTKIT_API_TAG_ID']); } /** @@ -1643,10 +1742,16 @@ public function testTagSubscriberByID() */ public function testTagSubscriberByIDWithInvalidTagID() { + // Create subscriber. + $emailAddress = $this->generateEmailAddress(); + $subscriber = $this->api->create_subscriber( + email_address: $emailAddress + ); + $this->expectException(ClientException::class); $result = $this->api->tag_subscriber_by_subscriber_id( tag_id: 12345, - subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] + subscriber_id: $subscriber->subscriber->id ); } @@ -1662,7 +1767,7 @@ public function testTagSubscriberByIDWithInvalidSubscriberID() { $this->expectException(ClientException::class); $result = $this->api->tag_subscriber_by_subscriber_id( - tag_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], + tag_id: $_ENV['CONVERTKIT_API_TAG_ID'], subscriber_id: 12345 ); } From c4e213ee1484e387233c4e46b12175f843378d6c Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 Mar 2024 17:37:32 +0000 Subject: [PATCH 18/78] Completed tests --- src/ConvertKit_API.php | 8 +- tests/ConvertKitAPITest.php | 302 +++++++++++++++++++++++++----------- 2 files changed, 217 insertions(+), 93 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 7cba215..cf80497 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -818,11 +818,11 @@ public function get_tag_subscriptions( if (!is_null($created_before)) { $options['created_before'] = $created_before->format('Y-m-d'); } - if (!is_null($added_after)) { - $options['added_after'] = $added_after->format('Y-m-d'); + if (!is_null($tagged_after)) { + $options['tagged_after'] = $tagged_after->format('Y-m-d'); } - if (!is_null($added_before)) { - $options['added_before'] = $added_before->format('Y-m-d'); + if (!is_null($tagged_before)) { + $options['tagged_before'] = $tagged_before->format('Y-m-d'); } // Build pagination parameters. diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 45b05fe..0097f84 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1781,27 +1781,28 @@ public function testTagSubscriberByIDWithInvalidSubscriberID() */ public function testRemoveTagFromSubscriber() { - // Tag the subscriber first. - $result = $this->api->tag_subscriber( - tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + // Create subscriber. + $emailAddress = $this->generateEmailAddress(); + $this->api->create_subscriber( + email_address: $emailAddress ); - // Get subscriber ID. - $subscriberID = $result->subscriber->id; + // Tag subscriber by email. + $subscriber = $this->api->tag_subscriber( + tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], + email: $emailAddress, + ); // Remove tag from subscriber. $result = $this->api->remove_tag_from_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - subscriber_id: $subscriberID + subscriber_id: $subscriber->subscriber->id ); // Confirm that the subscriber no longer has the tag. - $result = $this->api->get_subscriber_tags($subscriberID); - var_dump($result); - die(); + $result = $this->api->get_subscriber_tags($subscriber->subscriber->id); $this->assertIsArray($result->tags); - $this->assertEmpty($result->tags); + $this->assertCount(0, $result->tags); } /** @@ -1814,12 +1815,23 @@ public function testRemoveTagFromSubscriber() */ public function testRemoveTagFromSubscriberWithInvalidTagID() { - $this->markTestIncomplete(); + // Create subscriber. + $emailAddress = $this->generateEmailAddress(); + $this->api->create_subscriber( + email_address: $emailAddress + ); + + // Tag subscriber by email. + $subscriber = $this->api->tag_subscriber( + tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], + email: $emailAddress, + ); + // Remove tag from subscriber. $this->expectException(ClientException::class); $result = $this->api->remove_tag_from_subscriber( tag_id: 12345, - subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] + subscriber_id: $subscriber->subscriber->id ); } @@ -1833,8 +1845,6 @@ public function testRemoveTagFromSubscriberWithInvalidTagID() */ public function testRemoveTagFromSubscriberWithInvalidSubscriberID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $result = $this->api->remove_tag_from_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], @@ -1851,28 +1861,28 @@ public function testRemoveTagFromSubscriberWithInvalidSubscriberID() */ public function testRemoveTagFromSubscriberByEmail() { - $this->markTestIncomplete(); + // Create subscriber. + $emailAddress = $this->generateEmailAddress(); + $this->api->create_subscriber( + email_address: $emailAddress + ); - // Tag the subscriber first. - $email = $this->generateEmailAddress(); - $result = $this->api->tag_subscriber( + // Tag subscriber by email. + $subscriber = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $email + email: $emailAddress, ); - // Get subscriber ID. - $subscriberID = $result->subscription->subscriber->id; - // Remove tag from subscriber. - $result = $this->api->remove_tag_from_subscriber_by_email( + $result = $this->api->remove_tag_from_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $email + subscriber_id: $subscriber->subscriber->id ); // Confirm that the subscriber no longer has the tag. - $result = $this->api->get_subscriber_tags($subscriberID); + $result = $this->api->get_subscriber_tags($subscriber->subscriber->id); $this->assertIsArray($result->tags); - $this->assertEmpty($result->tags); + $this->assertCount(0, $result->tags); } /** @@ -1885,8 +1895,6 @@ public function testRemoveTagFromSubscriberByEmail() */ public function testRemoveTagFromSubscriberByEmailWithInvalidTagID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $result = $this->api->remove_tag_from_subscriber_by_email( tag_id: 12345, @@ -1894,6 +1902,23 @@ public function testRemoveTagFromSubscriberByEmailWithInvalidTagID() ); } + /** + * Test that remove_tag_from_subscriber() throws a ClientException when an invalid + * email address is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testRemoveTagFromSubscriberByEmailWithInvalidEmailAddress() + { + $this->expectException(ClientException::class); + $result = $this->api->remove_tag_from_subscriber_by_email( + tag_id: $_ENV['CONVERTKIT_API_TAG_ID'], + email: 'not-an-email-address' + ); + } + /** * Test that get_tag_subscriptions() returns the expected data * when a valid Tag ID is specified. @@ -1904,113 +1929,214 @@ public function testRemoveTagFromSubscriberByEmailWithInvalidTagID() */ public function testGetTagSubscriptions() { - $this->markTestIncomplete(); + $result = $this->api->get_tag_subscriptions( + tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'] + ); - $result = $this->api->get_tag_subscriptions((int) $_ENV['CONVERTKIT_API_TAG_ID']); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + } - // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. - $result = get_object_vars($result); - $this->assertArrayHasKey('total_subscriptions', $result); - $this->assertArrayHasKey('page', $result); - $this->assertArrayHasKey('total_pages', $result); - $this->assertArrayHasKey('subscriptions', $result); - $this->assertIsArray($result['subscriptions']); + /** + * Test that get_tag_subscriptions() returns the expected data + * when a valid Tag ID is specified and the subscription status + * is cancelled. + * + * @since 1.0.0 + * + * @return void + */ + public function testGetTagSubscriptionsWithCancelledSubscriberState() + { + $result = $this->api->get_tag_subscriptions( + tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], + subscriber_state: 'bounced' + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Check the correct subscribers were returned. + $this->assertEquals($result->subscribers[0]->state, 'bounced'); + } + + + /** + * Test that get_tag_subscriptions() returns the expected data + * when a valid Tag ID is specified and the added_after parameter + * is used. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetTagSubscriptionsWithTaggedAfterParam() + { + $date = new \DateTime('2024-01-01'); + $result = $this->api->get_tag_subscriptions( + tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], + tagged_after: $date + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); - // Assert sort order is ascending. + // Check the correct subscribers were returned. $this->assertGreaterThanOrEqual( - $result['subscriptions'][0]->created_at, - $result['subscriptions'][1]->created_at + $date->format('Y-m-d'), + date('Y-m-d', strtotime($result->subscribers[0]->tagged_at)) ); } /** * Test that get_tag_subscriptions() returns the expected data - * when a valid Tag ID is specified and the sort order is descending. + * when a valid Tag ID is specified and the tagged_before parameter + * is used. * - * @since 1.0.0 + * @since 2.0.0 * * @return void */ - public function testGetTagSubscriptionsWithDescSortOrder() + public function testGetTagSubscriptionsWithTaggedBeforeParam() { - $this->markTestIncomplete(); - + $date = new \DateTime('2024-01-01'); $result = $this->api->get_tag_subscriptions( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - sort_order: 'desc' + tagged_before: $date ); - // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. - $result = get_object_vars($result); - $this->assertArrayHasKey('total_subscriptions', $result); - $this->assertArrayHasKey('page', $result); - $this->assertArrayHasKey('total_pages', $result); - $this->assertArrayHasKey('subscriptions', $result); - $this->assertIsArray($result['subscriptions']); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); - // Assert sort order. + // Check the correct subscribers were returned. $this->assertLessThanOrEqual( - $result['subscriptions'][0]->created_at, - $result['subscriptions'][1]->created_at + $date->format('Y-m-d'), + date('Y-m-d', strtotime($result->subscribers[0]->tagged_at)) ); } /** * Test that get_tag_subscriptions() returns the expected data - * when a valid Tag ID is specified and the subscription status - * is cancelled. + * when a valid Tag ID is specified and the created_after parameter + * is used. * - * @since 1.0.0 + * @since 2.0.0 * * @return void */ - public function testGetTagSubscriptionsWithCancelledSubscriberState() + public function testGetTagSubscriptionsWithCreatedAfterParam() { - $this->markTestIncomplete(); + $date = new \DateTime('2024-01-01'); + $result = $this->api->get_tag_subscriptions( + tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], + created_after: $date + ); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Check the correct subscribers were returned. + $this->assertGreaterThanOrEqual( + $date->format('Y-m-d'), + date('Y-m-d', strtotime($result->subscribers[0]->created_at)) + ); + } + + /** + * Test that get_tag_subscriptions() returns the expected data + * when a valid Tag ID is specified and the created_before parameter + * is used. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetTagSubscriptionsWithCreatedBeforeParam() + { + $date = new \DateTime('2024-01-01'); $result = $this->api->get_tag_subscriptions( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - sort_order: 'asc', - subscriber_state: 'cancelled' + created_before: $date ); - // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. - $result = get_object_vars($result); - $this->assertArrayHasKey('total_subscriptions', $result); - $this->assertGreaterThan(1, $result['total_subscriptions']); - $this->assertArrayHasKey('page', $result); - $this->assertArrayHasKey('total_pages', $result); - $this->assertArrayHasKey('subscriptions', $result); - $this->assertIsArray($result['subscriptions']); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Check the correct subscribers were returned. + $this->assertLessThanOrEqual( + $date->format('Y-m-d'), + date('Y-m-d', strtotime($result->subscribers[0]->created_at)) + ); } /** * Test that get_tag_subscriptions() returns the expected data - * when a valid Tag ID is specified and the page is set to 2. + * when a valid Tag ID is specified and pagination parameters + * and per_page limits are specified. * * @since 1.0.0 * * @return void */ - public function testGetTagSubscriptionsWithPage() + public function testGetTagSubscriptionsPagination() { - $this->markTestIncomplete(); + $result = $this->api->get_tag_subscriptions( + tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], + per_page: 1 + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + // Assert a single subscriber was returned. + $this->assertCount(1, $result->subscribers); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. $result = $this->api->get_tag_subscriptions( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - sort_order: 'asc', - subscriber_state: 'active', - page: 2 + per_page: 1, + after_cursor: $result->pagination->end_cursor ); - // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. - $result = get_object_vars($result); - $this->assertArrayHasKey('total_subscriptions', $result); - $this->assertArrayHasKey('page', $result); - $this->assertEquals($result['page'], 2); - $this->assertArrayHasKey('total_pages', $result); - $this->assertArrayHasKey('subscriptions', $result); - $this->assertIsArray($result['subscriptions']); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert a single subscriber was returned. + $this->assertCount(1, $result->subscribers); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_tag_subscriptions( + tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert a single subscriber was returned. + $this->assertCount(1, $result->subscribers); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); } /** @@ -2021,10 +2147,8 @@ public function testGetTagSubscriptionsWithPage() * * @return void */ - public function testGetTagSubscriptionsWithInvalidFormID() + public function testGetTagSubscriptionsWithInvalidTagID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $result = $this->api->get_tag_subscriptions(12345); } From d6f77def6a41199112e2636c74c16ae024f7d771 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 25 Mar 2024 17:39:14 +0000 Subject: [PATCH 19/78] Coding standards --- src/ConvertKit_API.php | 19 +++++++++---------- tests/ConvertKitAPITest.php | 6 +++--- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index cf80497..921689b 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -668,17 +668,15 @@ public function create_tag(string $tag) { return $this->post( endpoint: 'tags', - args: [ - 'name' => $tag, - ] + args: ['name' => $tag] ); } /** * Creates multiple tags. * - * @param array $tags Tag Names. - * @param string $callback_url URL to notify for large batch size when async processing complete. + * @param array $tags Tag Names. + * @param string $callback_url URL to notify for large batch size when async processing complete. * * @since 1.1.0 * @@ -712,14 +710,15 @@ public function create_tags(array $tags, string $callback_url = '') /** * Tags a subscriber with the given existing Tag. * - * @param integer $tag_id Tag ID. - * @param string $email Email Address. + * @param integer $tag_id Tag ID. + * @param string $email Email Address. * * @see https://developers.convertkit.com/v4.html#tag-a-subscriber-by-email-address * * @return false|mixed */ - public function tag_subscriber(int $tag_id, string $email) { + public function tag_subscriber(int $tag_id, string $email) + { return $this->post( endpoint: sprintf('tags/%s/subscribers', $tag_id), args: ['email_address' => $email] @@ -729,8 +728,8 @@ public function tag_subscriber(int $tag_id, string $email) { /** * Tags a subscriber by subscriber ID with the given existing Tag. * - * @param integer $tag_id Tag ID. - * @param string $email Email Address. + * @param integer $tag_id Tag ID. + * @param integer $subscriber_id Subscriber ID. * * @see https://developers.convertkit.com/v4.html#tag-a-subscriber * diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 0097f84..eb8b04a 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1527,7 +1527,7 @@ public function testCreateTag() $this->assertArrayHasKey('id', $tag); $this->assertArrayHasKey('name', $tag); $this->assertArrayHasKey('created_at', $tag); - $this->assertEquals($tag['name'], $tagName); + $this->assertEquals($tag['name'], $tagName); } /** @@ -1641,7 +1641,7 @@ public function testTagSubscriber() $this->assertArrayHasKey('subscriber', get_object_vars($subscriber)); $this->assertArrayHasKey('id', get_object_vars($subscriber->subscriber)); $this->assertArrayHasKey('tagged_at', get_object_vars($subscriber->subscriber)); - + // Confirm the subscriber is tagged. $result = $this->api->get_subscriber_tags( subscriber_id: $subscriber->subscriber->id @@ -1718,7 +1718,7 @@ public function testTagSubscriberByID() $this->assertArrayHasKey('subscriber', get_object_vars($result)); $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); $this->assertArrayHasKey('tagged_at', get_object_vars($result->subscriber)); - + // Confirm the subscriber is tagged. $result = $this->api->get_subscriber_tags( subscriber_id: $result->subscriber->id From 3cb241a5d1499ba6544700f188587eacf3ed542d Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 13:34:43 +0000 Subject: [PATCH 20/78] Tests: Fix bulk tag tests --- src/ConvertKit_API.php | 2 +- tests/ConvertKitAPITest.php | 23 ++++++++++------------- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 921689b..1914369 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -691,7 +691,7 @@ public function create_tags(array $tags, string $callback_url = '') 'tags' => [], ]; foreach ($tags as $i => $tag) { - $options['tags'] = [ + $options['tags'][] = [ 'name' => (string) $tag, ]; } diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index eb8b04a..a95a62d 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1573,20 +1573,13 @@ public function testCreateTags() ]; $result = $this->api->create_tags($tagNames); - // Iterate through the results to confirm the tags were created. - foreach ($result->tags as $i => $tag) { - // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. - $tag = get_object_vars($tag); - $this->assertArrayHasKey('id', $tag); - $this->assertArrayHasKey('name', $tag); - $this->assertArrayHasKey('created_at', $tag); - $this->assertEquals($tag['name'], $tagNames[$i]); - } + // Assert no failures. + $this->assertCount(0, $result->failures); } /** - * Test that create_tags() throws a ClientException when creating - * blank tags. + * Test that create_tags() returns failures when attempting + * to create blank tags. * * @since 1.1.0 * @@ -1594,11 +1587,13 @@ public function testCreateTags() */ public function testCreateTagsBlank() { - $this->expectException(ClientException::class); $result = $this->api->create_tags([ '', '', ]); + + // Assert failures. + $this->assertCount(2, $result->failures); } /** @@ -1611,11 +1606,13 @@ public function testCreateTagsBlank() */ public function testCreateTagsThatExist() { - $this->expectException(ClientException::class); $result = $this->api->create_tags([ $_ENV['CONVERTKIT_API_TAG_NAME'], $_ENV['CONVERTKIT_API_TAG_NAME_2'], ]); + + // Assert failures. + $this->assertCount(2, $result->failures); } /** From 142217f13ec65f934fb6f17fad32f432d185f3d3 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 13:56:48 +0000 Subject: [PATCH 21/78] Started work on custom fields endpoint --- src/ConvertKit_API.php | 78 +++++++++++++++++++------ tests/ConvertKitAPITest.php | 111 ++++++++++++++++++++++++++++++------ 2 files changed, 152 insertions(+), 37 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index fa06d23..8cb9722 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1511,16 +1511,42 @@ public function destroy_webhook(int $rule_id) /** * List custom fields. - * + * * @since 1.0.0 * - * @see https://developers.convertkit.com/#list-fields + * @param bool $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * - * @return false|object + * @see https://developers.convertkit.com/v4.html#list-custom-fields + * + * @return false|mixed */ - public function get_custom_fields() - { - return $this->get('custom_fields'); + public function get_custom_fields( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Build parameters. + $options = [ + 'include_total_count' => $include_total_count, + ]; + + // Build pagination parameters. + $options = $this->build_pagination_params( + params: $options, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ); + + // Send request. + return $this->get( + endpoint: 'custom_fields', + args: $options + ); } /** @@ -1530,16 +1556,16 @@ public function get_custom_fields() * * @since 1.0.0 * - * @see https://developers.convertkit.com/#create-field + * @see https://developers.convertkit.com/v4.html#create-a-custom-field * * @return false|object */ public function create_custom_field(string $label) { return $this->post( - 'custom_fields', - [ - 'label' => [$label], + endpoint: 'custom_fields', + args: [ + 'label' => $label, ] ); } @@ -1547,19 +1573,35 @@ public function create_custom_field(string $label) /** * Creates multiple custom fields. * - * @param array $labels Custom Fields labels. + * @param array $labels Custom Fields labels. + * @param string $callback_url URL to notify for large batch size when async processing complete. * * @since 1.0.0 * - * @see https://developers.convertkit.com/#create-field + * @see https://developers.convertkit.com/v4.html#bulk-create-custom-fields * * @return false|object */ - public function create_custom_fields(array $labels) + public function create_custom_fields(array $labels, string $callback_url = '') { + // Build parameters. + $options = [ + 'custom_fields' => [], + ]; + foreach ($labels as $i => $label) { + $options['custom_fields'][] = [ + 'label' => (string) $label, + ]; + } + + if (!empty($callback_url)) { + $options['callback_url'] = $callback_url; + } + + // Send request. return $this->post( - 'custom_fields', - ['label' => $labels] + endpoint: 'bulk/custom_fields', + args: $options ); } @@ -1571,15 +1613,15 @@ public function create_custom_fields(array $labels) * * @since 1.0.0 * - * @see https://developers.convertkit.com/#update-field + * @see https://developers.convertkit.com/v4.html#update-a-custom-field * * @return false|object */ public function update_custom_field(int $id, string $label) { return $this->put( - sprintf('custom_fields/%s', $id), - ['label' => $label] + endpoint: sprintf('custom_fields/%s', $id), + args: ['label' => $label] ); } diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 8b2b2ff..b51df6f 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -3343,18 +3343,94 @@ public function testDestroyWebhookWithInvalidRuleID() */ public function testGetCustomFields() { - $this->markTestIncomplete(); - $result = $this->api->get_custom_fields(); - $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('custom_fields', get_object_vars($result)); - // Inspect first custom field. - $customField = get_object_vars($result->custom_fields[0]); - $this->assertArrayHasKey('id', $customField); - $this->assertArrayHasKey('name', $customField); - $this->assertArrayHasKey('key', $customField); - $this->assertArrayHasKey('label', $customField); + // Assert custom fields and pagination exist. + $this->assertDataExists($result, 'custom_fields'); + $this->assertPaginationExists($result); + } + + /** + * Test that get_custom_fields() returns the expected data + * when the total count is included. + * + * @since 1.0.0 + * + * @return void + */ + public function testGetCustomFieldsWithTotalCount() + { + $result = $this->api->get_custom_fields( + include_total_count: true + ); + + // Assert custom fields and pagination exist. + $this->assertDataExists($result, 'custom_fields'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + + /** + * Test that get_custom_fields() returns the expected data + * when pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetCustomFieldsPagination() + { + $result = $this->api->get_custom_fields( + per_page: 1 + ); + + // Assert custom fields and pagination exist. + $this->assertDataExists($result, 'custom_fields'); + $this->assertPaginationExists($result); + + // Assert a single custom field was returned. + $this->assertCount(1, $result->custom_fields); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->get_custom_fields( + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert custom fields and pagination exist. + $this->assertDataExists($result, 'custom_fields'); + $this->assertPaginationExists($result); + + // Assert a single custom field was returned. + $this->assertCount(1, $result->custom_fields); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_custom_fields( + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert custom fields and pagination exist. + $this->assertDataExists($result, 'custom_fields'); + $this->assertPaginationExists($result); + + // Assert a single custom field was returned. + $this->assertCount(1, $result->custom_fields); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); } /** @@ -3366,12 +3442,10 @@ public function testGetCustomFields() */ public function testCreateCustomField() { - $this->markTestIncomplete(); - $label = 'Custom Field ' . mt_rand(); $result = $this->api->create_custom_field($label); - $result = get_object_vars($result); + $result = get_object_vars($result->custom_field); $this->assertArrayHasKey('id', $result); $this->assertArrayHasKey('name', $result); $this->assertArrayHasKey('key', $result); @@ -3392,8 +3466,6 @@ public function testCreateCustomField() */ public function testCreateCustomFieldWithBlankLabel() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $this->api->create_custom_field(''); } @@ -3407,17 +3479,18 @@ public function testCreateCustomFieldWithBlankLabel() */ public function testCreateCustomFields() { - $this->markTestIncomplete(); - $labels = [ 'Custom Field ' . mt_rand(), 'Custom Field ' . mt_rand(), ]; $result = $this->api->create_custom_fields($labels); + // Assert no failures. + $this->assertCount(0, $result->failures); + // Confirm result is an array comprising of each custom field that was created. - $this->assertIsArray($result); - foreach ($result as $index => $customField) { + $this->assertIsArray($result->custom_fields); + foreach ($result->custom_fields as $index => $customField) { // Confirm individual custom field. $customField = get_object_vars($customField); $this->assertArrayHasKey('id', $customField); From 43c16766b4165401200b9876f50b9abead2e80db Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 14:00:58 +0000 Subject: [PATCH 22/78] Finished tests, coding standards and PHPStan compat. --- src/ConvertKit_API.php | 34 +++++++++++++++------------------- tests/ConvertKitAPITest.php | 12 ++---------- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 8cb9722..9415747 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1511,13 +1511,13 @@ public function destroy_webhook(int $rule_id) /** * List custom fields. - * - * @since 1.0.0 * - * @param bool $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 1.0.0 * * @see https://developers.convertkit.com/v4.html#list-custom-fields * @@ -1530,9 +1530,7 @@ public function get_custom_fields( int $per_page = 100 ) { // Build parameters. - $options = [ - 'include_total_count' => $include_total_count, - ]; + $options = ['include_total_count' => $include_total_count]; // Build pagination parameters. $options = $this->build_pagination_params( @@ -1564,9 +1562,7 @@ public function create_custom_field(string $label) { return $this->post( endpoint: 'custom_fields', - args: [ - 'label' => $label, - ] + args: ['label' => $label] ); } @@ -1824,14 +1820,14 @@ private function strip_html_head_body_tags(string $markup) /** * Adds pagination parameters to the given array of existing API parameters. * - * @param array $params API parameters. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param array $params API parameters. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @since 2.0.0 * - * @return array + * @return array */ private function build_pagination_params( array $params = [], @@ -1855,8 +1851,8 @@ private function build_pagination_params( /** * Performs a GET request to the API. * - * @param string $endpoint API Endpoint. - * @param array|string> $args Request arguments. + * @param string $endpoint API Endpoint. + * @param array|string> $args Request arguments. * * @return false|mixed */ diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index b51df6f..6776ea4 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -3515,12 +3515,10 @@ public function testCreateCustomFields() */ public function testUpdateCustomField() { - $this->markTestIncomplete(); - // Create custom field. $label = 'Custom Field ' . mt_rand(); $result = $this->api->create_custom_field($label); - $id = $result->id; + $id = $result->custom_field->id; // Change label. $newLabel = 'Custom Field ' . mt_rand(); @@ -3548,8 +3546,6 @@ public function testUpdateCustomField() */ public function testUpdateCustomFieldWithInvalidID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $this->api->update_custom_field(12345, 'Something'); } @@ -3563,12 +3559,10 @@ public function testUpdateCustomFieldWithInvalidID() */ public function testDeleteCustomField() { - $this->markTestIncomplete(); - // Create custom field. $label = 'Custom Field ' . mt_rand(); $result = $this->api->create_custom_field($label); - $id = $result->id; + $id = $result->custom_field->id; // Delete custom field as tests passed. $this->api->delete_custom_field($id); @@ -3590,8 +3584,6 @@ public function testDeleteCustomField() */ public function testDeleteCustomFieldWithInvalidID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $this->api->delete_custom_field(12345); } From c63ca41fe113b8a989fdfe264a0041963c067969 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 14:42:32 +0000 Subject: [PATCH 23/78] Started work on purchases endpoint --- src/ConvertKit_API.php | 82 ++++++++++++++--- tests/ConvertKitAPITest.php | 170 +++++++++++++++++++++++++----------- 2 files changed, 192 insertions(+), 60 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index fa06d23..ebcad7d 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1602,15 +1602,39 @@ public function delete_custom_field(int $id) /** * List purchases. * - * @param array $options Request options. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * - * @see https://developers.convertkit.com/#list-purchases + * @since 1.0.0 * - * @return false|object + * @see https://developers.convertkit.com/v4.html#list-purchases + * + * @return false|mixed */ - public function list_purchases(array $options) - { - return $this->get('purchases', $options); + public function list_purchases( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Build parameters. + $options = ['include_total_count' => $include_total_count]; + + // Build pagination parameters. + $options = $this->build_pagination_params( + params: $options, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ); + + // Send request. + return $this->get( + endpoint: 'purchases', + args: $options + ); } /** @@ -1618,7 +1642,7 @@ public function list_purchases(array $options) * * @param integer $purchase_id Purchase ID. * - * @see https://developers.convertkit.com/#retrieve-a-specific-purchase + * @see https://developers.convertkit.com/v4.html#get-a-purchase * * @return false|object */ @@ -1630,14 +1654,50 @@ public function get_purchase(int $purchase_id) /** * Creates a purchase. * - * @param array $options Purchase data. + * @param string $email_address Email Address. + * @param string $transaction_id Transaction ID. + * @param string $status Order Status. + * @param float $subtotal Subtotal. + * @param float $tax Tax. + * @param float $shipping Shipping. + * @param float $discount Discount. + * @param float $total Total. + * @param string $currency ISO Currency Code. + * @param \DateTime $transaction_time Transaction date and time. + * @param array $products Products. * - * @see https://developers.convertkit.com/#create-a-purchase + * @see https://developers.convertkit.com/v4.html#create-a-purchase * * @return false|object */ - public function create_purchase(array $options) - { + public function create_purchase( + string $email_address, + string $transaction_id, + string $status, + float $subtotal = 0, + float $tax = 0, + float $shipping = 0, + float $discount = 0, + float $total = 0, + string $currency = 'usd', + \DateTime $transaction_time = null, + array $products = [] + ) { + // Build parameters. + $options = [ + 'email_address' => $email_address, + 'transaction_id' => $transaction_id, + 'status' => $status, + 'subtotal' => $subtotal, + 'tax' => $tax, + 'shipping' => $shipping, + 'discount' => $discount, + 'total' => $total, + 'currency' => $currency, + 'transaction_time' => $transaction_time->format('Y-m-d H:i:s'), + 'products' => $products, + ]; + return $this->post('purchases', $options); } diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 8b2b2ff..b2e129c 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -3532,16 +3532,94 @@ public function testDeleteCustomFieldWithInvalidID() */ public function testListPurchases() { - $this->markTestIncomplete(); + $result = $this->api->list_purchases(); - $purchases = $this->api->list_purchases([ - 'page' => 1, - ]); - $this->assertInstanceOf('stdClass', $purchases); - $this->assertArrayHasKey('total_purchases', get_object_vars($purchases)); - $this->assertArrayHasKey('page', get_object_vars($purchases)); - $this->assertArrayHasKey('total_pages', get_object_vars($purchases)); - $this->assertArrayHasKey('purchases', get_object_vars($purchases)); + // Assert purchases and pagination exist. + $this->assertDataExists($result, 'purchases'); + $this->assertPaginationExists($result); + } + + /** + * Test that list_purchases() returns the expected data + * when the total count is included. + * + * @since 1.0.0 + * + * @return void + */ + public function testListPurchasesWithTotalCount() + { + $result = $this->api->list_purchases( + include_total_count: true + ); + + // Assert purchases and pagination exist. + $this->assertDataExists($result, 'purchases'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + + /** + * Test that list_purchases() returns the expected data + * when pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testListPurchasesPagination() + { + $result = $this->api->list_purchases( + per_page: 1 + ); + + // Assert purchases and pagination exist. + $this->assertDataExists($result, 'purchases'); + $this->assertPaginationExists($result); + + // Assert a single purchase was returned. + $this->assertCount(1, $result->purchases); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->list_purchases( + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert purchases and pagination exist. + $this->assertDataExists($result, 'purchases'); + $this->assertPaginationExists($result); + + // Assert a single purchase was returned. + $this->assertCount(1, $result->purchases); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->list_purchases( + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert purchases and pagination exist. + $this->assertDataExists($result, 'purchases'); + $this->assertPaginationExists($result); + + // Assert a single purchase was returned. + $this->assertCount(1, $result->purchases); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); } /** @@ -3553,12 +3631,10 @@ public function testListPurchases() */ public function testGetPurchase() { - $this->markTestIncomplete(); - // Get ID of first purchase. - $purchases = $this->api->list_purchases([ - 'page' => 1, - ]); + $purchases = $this->api->list_purchases( + per_page: 1 + ); $id = $purchases->purchases[0]->id; // Get purchase. @@ -3577,8 +3653,6 @@ public function testGetPurchase() */ public function testGetPurchaseWithInvalidID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $this->api->get_purchase(12345); } @@ -3592,41 +3666,39 @@ public function testGetPurchaseWithInvalidID() */ public function testCreatePurchase() { - $this->markTestIncomplete(); - - $purchase = $this->api->create_purchase([ - 'purchase' => [ - 'transaction_id' => str_shuffle('wfervdrtgsdewrafvwefds'), - 'email_address' => $this->generateEmailAddress(), - 'first_name' => 'John', - 'currency' => 'usd', - 'transaction_time' => date('Y-m-d H:i:s'), - 'subtotal' => 20.00, - 'tax' => 2.00, - 'shipping' => 2.00, - 'discount' => 3.00, - 'total' => 21.00, - 'status' => 'paid', - 'products' => [ - [ - 'pid' => 9999, - 'lid' => 7777, - 'name' => 'Floppy Disk (512k)', - 'sku' => '7890-ijkl', - 'unit_price' => 5.00, - 'quantity' => 2 - ], - [ - 'pid' => 5555, - 'lid' => 7778, - 'name' => 'Telephone Cord (data)', - 'sku' => 'mnop-1234', - 'unit_price' => 10.00, - 'quantity' => 1 - ], + $purchase = $this->api->create_purchase( + email_address: $this->generateEmailAddress(), + transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'), + status: 'paid', + subtotal: 20.00, + tax: 2.00, + shipping: 2.00, + discount: 3.00, + total: 21.00, + currency: 'usd', + transaction_time: new DateTime('now'), + products: [ + [ + 'name' => 'Floppy Disk (512k)', + 'pid' => 9999, + 'lid' => 7777, + 'quantity' => 2, + 'unit_price' => 5.00, + + ], + [ + 'name' => 'Telephone Cord (data)', + 'pid' => 5555, + 'lid' => 7778, + 'quantity' => 1 + 'unit_price' => 10.00, + ], ], - ]); + ); + var_dump($purchase); + die(); + $this->assertInstanceOf('stdClass', $purchase); $this->assertArrayHasKey('transaction_id', get_object_vars($purchase)); } From 6e5fbb41b03cfe452931e6eb2fffb59a28a782ac Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 14:57:03 +0000 Subject: [PATCH 24/78] Completed tests --- src/ConvertKit_API.php | 2 +- tests/ConvertKitAPITest.php | 24 +++++++++--------------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index ebcad7d..5109a2a 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1694,7 +1694,7 @@ public function create_purchase( 'discount' => $discount, 'total' => $total, 'currency' => $currency, - 'transaction_time' => $transaction_time->format('Y-m-d H:i:s'), + 'transaction_time' => (!is_null($transaction_time) ? $transaction_time->format('Y-m-d H:i:s') : ''), 'products' => $products, ]; diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index b2e129c..c1bc71d 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -3640,7 +3640,7 @@ public function testGetPurchase() // Get purchase. $result = $this->api->get_purchase($id); $this->assertInstanceOf('stdClass', $result); - $this->assertEquals($result->id, $id); + $this->assertEquals($purchases->purchases[0]->id, $id); } /** @@ -3684,23 +3684,19 @@ public function testCreatePurchase() 'lid' => 7777, 'quantity' => 2, 'unit_price' => 5.00, - ], [ 'name' => 'Telephone Cord (data)', 'pid' => 5555, 'lid' => 7778, - 'quantity' => 1 + 'quantity' => 1, 'unit_price' => 10.00, - ], ], ); - var_dump($purchase); - die(); $this->assertInstanceOf('stdClass', $purchase); - $this->assertArrayHasKey('transaction_id', get_object_vars($purchase)); + $this->assertArrayHasKey('transaction_id', get_object_vars($purchase->purchase)); } /** @@ -3711,16 +3707,14 @@ public function testCreatePurchase() * * @return void */ - public function testCreatePurchaseWithMissingData() + public function testCreatePurchaseWithInvalidData() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); - $this->api->create_purchase([ - 'invalid-key' => [ - 'transaction_id' => str_shuffle('wfervdrtgsdewrafvwefds'), - ], - ]); + $this->api->create_purchase( + email_address: 'not-an-email-address', + transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'), + status: 'paid' + ); } /** From 484e4686ad1a6696663fc7cb14fb3c007e6ee26a Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 14:59:43 +0000 Subject: [PATCH 25/78] Coding standards --- src/ConvertKit_API.php | 10 +++++----- tests/ConvertKitAPITest.php | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 5109a2a..eef95d6 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1842,14 +1842,14 @@ private function strip_html_head_body_tags(string $markup) /** * Adds pagination parameters to the given array of existing API parameters. * - * @param array $params API parameters. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param array $params API parameters. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @since 2.0.0 * - * @return array + * @return array */ private function build_pagination_params( array $params = [], diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index c1bc71d..19348fc 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -3694,7 +3694,7 @@ public function testCreatePurchase() ], ], ); - + $this->assertInstanceOf('stdClass', $purchase); $this->assertArrayHasKey('transaction_id', get_object_vars($purchase->purchase)); } From 23b7acb7147cdbbd4b8b5d5f22144b2c8210ab43 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 15:47:00 +0000 Subject: [PATCH 26/78] Fix test name --- tests/ConvertKitAPITest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index a95a62d..4cd63a7 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1938,13 +1938,13 @@ public function testGetTagSubscriptions() /** * Test that get_tag_subscriptions() returns the expected data * when a valid Tag ID is specified and the subscription status - * is cancelled. + * is bounced. * * @since 1.0.0 * * @return void */ - public function testGetTagSubscriptionsWithCancelledSubscriberState() + public function testGetTagSubscriptionsWithBouncedSubscriberState() { $result = $this->api->get_tag_subscriptions( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], From ba72fd250052f76bca06be7b670f6ec212899cd9 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 16:02:42 +0000 Subject: [PATCH 27/78] Rename `list_purchases` to `get_purchases`, to match other get methods --- src/ConvertKit_API.php | 2 +- tests/ConvertKitAPITest.php | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index eef95d6..78f5f16 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1613,7 +1613,7 @@ public function delete_custom_field(int $id) * * @return false|mixed */ - public function list_purchases( + public function get_purchases( bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 19348fc..b68d214 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -3524,15 +3524,15 @@ public function testDeleteCustomFieldWithInvalidID() } /** - * Test that list_purchases() returns the expected data. + * Test that get_purchases() returns the expected data. * * @since 1.0.0 * * @return void */ - public function testListPurchases() + public function testGetPurchases() { - $result = $this->api->list_purchases(); + $result = $this->api->get_purchases(); // Assert purchases and pagination exist. $this->assertDataExists($result, 'purchases'); @@ -3540,16 +3540,16 @@ public function testListPurchases() } /** - * Test that list_purchases() returns the expected data + * Test that get_purchases() returns the expected data * when the total count is included. * * @since 1.0.0 * * @return void */ - public function testListPurchasesWithTotalCount() + public function testGetPurchasesWithTotalCount() { - $result = $this->api->list_purchases( + $result = $this->api->get_purchases( include_total_count: true ); @@ -3563,16 +3563,16 @@ public function testListPurchasesWithTotalCount() } /** - * Test that list_purchases() returns the expected data + * Test that get_purchases() returns the expected data * when pagination parameters and per_page limits are specified. * * @since 2.0.0 * * @return void */ - public function testListPurchasesPagination() + public function testGetPurchasesPagination() { - $result = $this->api->list_purchases( + $result = $this->api->get_purchases( per_page: 1 ); @@ -3588,7 +3588,7 @@ public function testListPurchasesPagination() $this->assertTrue($result->pagination->has_next_page); // Use pagination to fetch next page. - $result = $this->api->list_purchases( + $result = $this->api->get_purchases( per_page: 1, after_cursor: $result->pagination->end_cursor ); @@ -3605,7 +3605,7 @@ public function testListPurchasesPagination() $this->assertTrue($result->pagination->has_next_page); // Use pagination to fetch previous page. - $result = $this->api->list_purchases( + $result = $this->api->get_purchases( per_page: 1, before_cursor: $result->pagination->start_cursor ); @@ -3632,7 +3632,7 @@ public function testListPurchasesPagination() public function testGetPurchase() { // Get ID of first purchase. - $purchases = $this->api->list_purchases( + $purchases = $this->api->get_purchases( per_page: 1 ); $id = $purchases->purchases[0]->id; From f292de3d41424faa716ddd77d6c2b50a389d12d8 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 16:15:12 +0000 Subject: [PATCH 28/78] Standardise method names for subscribers; append `_by_email` to secondary subscriber methods by email --- phpstan.neon.dist | 7 +---- phpstan.neon.example | 7 +---- src/ConvertKit_API.php | 46 ++++++++++++++--------------- tests/ConvertKitAPITest.php | 58 ++++++++++++++++++------------------- 4 files changed, 54 insertions(+), 64 deletions(-) diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 5773b72..c3bd077 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -6,9 +6,4 @@ parameters: # Should not need to edit anything below here # Rule Level: https://phpstan.org/user-guide/rule-levels - level: 8 - - # Ignore the following errors, as PHPStan either does not have registered symbols for them yet, - # or the symbols are inaccurate. - ignoreErrors: - - '#\$headers of class GuzzleHttp\\Psr7\\Request constructor expects#' \ No newline at end of file + level: 8 \ No newline at end of file diff --git a/phpstan.neon.example b/phpstan.neon.example index 5773b72..c3bd077 100644 --- a/phpstan.neon.example +++ b/phpstan.neon.example @@ -6,9 +6,4 @@ parameters: # Should not need to edit anything below here # Rule Level: https://phpstan.org/user-guide/rule-levels - level: 8 - - # Ignore the following errors, as PHPStan either does not have registered symbols for them yet, - # or the symbols are inaccurate. - ignoreErrors: - - '#\$headers of class GuzzleHttp\\Psr7\\Request constructor expects#' \ No newline at end of file + level: 8 \ No newline at end of file diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 881ed4b..192bfc1 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -416,18 +416,18 @@ public function get_landing_pages() /** * Adds a subscriber to a form by email address * - * @param integer $form_id Form ID. - * @param string $email Email Address. + * @param integer $form_id Form ID. + * @param string $email_address Email Address. * * @see https://developers.convertkit.com/v4.html#add-subscriber-to-form-by-email-address * * @return false|mixed */ - public function add_subscriber_to_form(int $form_id, string $email) + public function add_subscriber_to_form_by_email(int $form_id, string $email_address) { return $this->post( endpoint: sprintf('forms/%s/subscribers', $form_id), - args: ['email_address' => $email] + args: ['email_address' => $email_address] ); } @@ -443,7 +443,7 @@ public function add_subscriber_to_form(int $form_id, string $email) * * @return false|mixed */ - public function add_subscriber_to_form_by_subscriber_id(int $form_id, int $subscriber_id) + public function add_subscriber_to_form(int $form_id, int $subscriber_id) { return $this->post(sprintf('forms/%s/subscribers/%s', $form_id, $subscriber_id)); } @@ -536,18 +536,18 @@ public function get_sequences(string $after_cursor = '', string $before_cursor = /** * Adds a subscriber to a sequence by email address * - * @param integer $sequence_id Sequence ID. - * @param string $email Email Address. + * @param integer $sequence_id Sequence ID. + * @param string $email_address Email Address. * * @see https://developers.convertkit.com/v4.html#add-subscriber-to-sequence-by-email-address * * @return false|mixed */ - public function add_subscriber_to_sequence(int $sequence_id, string $email) + public function add_subscriber_to_sequence_by_email(int $sequence_id, string $email_address) { return $this->post( endpoint: sprintf('sequences/%s/subscribers', $sequence_id), - args: ['email_address' => $email] + args: ['email_address' => $email_address] ); } @@ -563,7 +563,7 @@ public function add_subscriber_to_sequence(int $sequence_id, string $email) * * @return false|mixed */ - public function add_subscriber_to_sequence_by_subscriber_id(int $sequence_id, int $subscriber_id) + public function add_subscriber_to_sequence(int $sequence_id, int $subscriber_id) { return $this->post(sprintf('sequences/%s/subscribers/%s', $sequence_id, $subscriber_id)); } @@ -710,18 +710,18 @@ public function create_tags(array $tags, string $callback_url = '') /** * Tags a subscriber with the given existing Tag. * - * @param integer $tag_id Tag ID. - * @param string $email Email Address. + * @param integer $tag_id Tag ID. + * @param string $email_address Email Address. * * @see https://developers.convertkit.com/v4.html#tag-a-subscriber-by-email-address * * @return false|mixed */ - public function tag_subscriber(int $tag_id, string $email) + public function tag_subscriber_by_email(int $tag_id, string $email_address) { return $this->post( endpoint: sprintf('tags/%s/subscribers', $tag_id), - args: ['email_address' => $email] + args: ['email_address' => $email_address] ); } @@ -735,7 +735,7 @@ public function tag_subscriber(int $tag_id, string $email) * * @return false|mixed */ - public function tag_subscriber_by_subscriber_id(int $tag_id, int $subscriber_id) + public function tag_subscriber(int $tag_id, int $subscriber_id) { return $this->post(sprintf('tags/%s/subscribers/%s', $tag_id, $subscriber_id)); } @@ -760,8 +760,8 @@ public function remove_tag_from_subscriber(int $tag_id, int $subscriber_id) /** * Removes a tag from a subscriber by email address. * - * @param integer $tag_id Tag ID. - * @param string $email Subscriber email address. + * @param integer $tag_id Tag ID. + * @param string $email_address Subscriber email address. * * @since 1.0.0 * @@ -769,11 +769,11 @@ public function remove_tag_from_subscriber(int $tag_id, int $subscriber_id) * * @return false|mixed */ - public function remove_tag_from_subscriber_by_email(int $tag_id, string $email) + public function remove_tag_from_subscriber_by_email(int $tag_id, string $email_address) { return $this->delete( sprintf('tags/%s/subscribers', $tag_id), - ['email_address' => $email] + ['email_address' => $email_address] ); } @@ -1186,18 +1186,18 @@ public function update_subscriber( /** * Unsubscribe an email address. * - * @param string $email Email Address. + * @param string $email_address Email Address. * * @see https://developers.convertkit.com/v4.html#unsubscribe-subscriber * * @return false|object */ - public function unsubscribe(string $email) + public function unsubscribe_by_email(string $email_address) { return $this->post( sprintf( 'subscribers/%s/unsubscribe', - $this->get_subscriber_id($email) + $this->get_subscriber_id($email_address) ) ); } @@ -1211,7 +1211,7 @@ public function unsubscribe(string $email) * * @return false|object */ - public function unsubscribe_by_id(int $subscriber_id) + public function unsubscribe(int $subscriber_id) { return $this->post(sprintf('subscribers/%s/unsubscribe', $subscriber_id)); } diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index b049d03..e477c88 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1108,7 +1108,7 @@ public function testAddSubscriberToSequenceWithInvalidEmailAddress() } /** - * Test that add_subscriber_to_sequence_by_subscriber_id() returns the expected data. + * Test that add_subscriber_to_sequence() returns the expected data. * * @since 2.0.0 * @@ -1116,7 +1116,7 @@ public function testAddSubscriberToSequenceWithInvalidEmailAddress() */ public function testAddSubscriberToSequenceByID() { - $result = $this->api->add_subscriber_to_sequence_by_subscriber_id( + $result = $this->api->add_subscriber_to_sequence( sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] ); @@ -1127,7 +1127,7 @@ public function testAddSubscriberToSequenceByID() } /** - * Test that add_subscriber_to_sequence_by_subscriber_id() throws a ClientException when an invalid + * Test that add_subscriber_to_sequence() throws a ClientException when an invalid * sequence ID is specified. * * @since 2.0.0 @@ -1137,14 +1137,14 @@ public function testAddSubscriberToSequenceByID() public function testAddSubscriberToSequenceByIDWithInvalidSequenceID() { $this->expectException(ClientException::class); - $result = $this->api->add_subscriber_to_sequence_by_subscriber_id( + $result = $this->api->add_subscriber_to_sequence( sequence_id: 12345, subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] ); } /** - * Test that add_subscriber_to_sequence_by_subscriber_id() throws a ClientException when an invalid + * Test that add_subscriber_to_sequence() throws a ClientException when an invalid * email address is specified. * * @since 2.0.0 @@ -1154,7 +1154,7 @@ public function testAddSubscriberToSequenceByIDWithInvalidSequenceID() public function testAddSubscriberToSequenceByIDWithInvalidSubscriberID() { $this->expectException(ClientException::class); - $result = $this->api->add_subscriber_to_sequence_by_subscriber_id( + $result = $this->api->add_subscriber_to_sequence( sequence_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], subscriber_id: 12345 ); @@ -1693,7 +1693,7 @@ public function testTagSubscriberWithInvalidEmailAddress() } /** - * Test that tag_subscriber_by_subscriber_id() returns the expected data. + * Test that tag_subscriber() returns the expected data. * * @since 2.0.0 * @@ -1708,7 +1708,7 @@ public function testTagSubscriberByID() ); // Tag subscriber by email. - $result = $this->api->tag_subscriber_by_subscriber_id( + $result = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], subscriber_id: $subscriber->subscriber->id, ); @@ -1730,7 +1730,7 @@ public function testTagSubscriberByID() } /** - * Test that tag_subscriber_by_subscriber_id() throws a ClientException when an invalid + * Test that tag_subscriber() throws a ClientException when an invalid * sequence ID is specified. * * @since 2.0.0 @@ -1746,14 +1746,14 @@ public function testTagSubscriberByIDWithInvalidTagID() ); $this->expectException(ClientException::class); - $result = $this->api->tag_subscriber_by_subscriber_id( + $result = $this->api->tag_subscriber( tag_id: 12345, subscriber_id: $subscriber->subscriber->id ); } /** - * Test that tag_subscriber_by_subscriber_id() throws a ClientException when an invalid + * Test that tag_subscriber() throws a ClientException when an invalid * email address is specified. * * @since 2.0.0 @@ -1763,7 +1763,7 @@ public function testTagSubscriberByIDWithInvalidTagID() public function testTagSubscriberByIDWithInvalidSubscriberID() { $this->expectException(ClientException::class); - $result = $this->api->tag_subscriber_by_subscriber_id( + $result = $this->api->tag_subscriber( tag_id: $_ENV['CONVERTKIT_API_TAG_ID'], subscriber_id: 12345 ); @@ -2272,7 +2272,7 @@ public function testAddSubscriberToFormWithInvalidEmailAddress() } /** - * Test that add_subscriber_to_form_by_subscriber_id() returns the expected data. + * Test that add_subscriber_to_form() returns the expected data. * * @since 2.0.0 * @@ -2280,7 +2280,7 @@ public function testAddSubscriberToFormWithInvalidEmailAddress() */ public function testAddSubscriberToFormByID() { - $result = $this->api->add_subscriber_to_form_by_subscriber_id( + $result = $this->api->add_subscriber_to_form( form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'], subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] ); @@ -2291,7 +2291,7 @@ public function testAddSubscriberToFormByID() } /** - * Test that add_subscriber_to_form_by_subscriber_id() throws a ClientException when an invalid + * Test that add_subscriber_to_form() throws a ClientException when an invalid * form ID is specified. * * @since 2.0.0 @@ -2301,14 +2301,14 @@ public function testAddSubscriberToFormByID() public function testAddSubscriberToFormByIDWithInvalidFormID() { $this->expectException(ClientException::class); - $result = $this->api->add_subscriber_to_form_by_subscriber_id( + $result = $this->api->add_subscriber_to_form( form_id: 12345, subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] ); } /** - * Test that add_subscriber_to_form_by_subscriber_id() throws a ClientException when an invalid + * Test that add_subscriber_to_form() throws a ClientException when an invalid * email address is specified. * * @since 2.0.0 @@ -2318,7 +2318,7 @@ public function testAddSubscriberToFormByIDWithInvalidFormID() public function testAddSubscriberToFormByIDWithInvalidSubscriberID() { $this->expectException(ClientException::class); - $result = $this->api->add_subscriber_to_form_by_subscriber_id( + $result = $this->api->add_subscriber_to_form( form_id: $_ENV['CONVERTKIT_API_FORM_ID'], subscriber_id: 12345 ); @@ -2687,7 +2687,7 @@ public function testCreateSubscriber() $this->assertEquals($result->subscriber->email_address, $emailAddress); // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); + $this->api->unsubscribe($result->subscriber->id); } /** @@ -2712,7 +2712,7 @@ public function testCreateSubscriberWithFirstName() $this->assertEquals($result->subscriber->first_name, $firstName); // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); + $this->api->unsubscribe($result->subscriber->id); } /** @@ -2737,7 +2737,7 @@ public function testCreateSubscriberWithSubscriberState() $this->assertEquals($result->subscriber->state, $subscriberState); // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); + $this->api->unsubscribe($result->subscriber->id); } /** @@ -2764,7 +2764,7 @@ public function testCreateSubscriberWithCustomFields() $this->assertEquals($result->subscriber->fields->last_name, $lastName); // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); + $this->api->unsubscribe($result->subscriber->id); } /** @@ -2824,7 +2824,7 @@ public function testCreateSubscriberWithInvalidCustomFields() $this->assertEquals($result->subscriber->email_address, $emailAddress); // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); + $this->api->unsubscribe($result->subscriber->id); } /** @@ -2854,7 +2854,7 @@ public function testCreateSubscribers() $this->assertEquals($subscriber->email_address, $subscribers[$i]['email_address']); // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($subscriber->id); + $this->api->unsubscribe($subscriber->id); } } @@ -3019,7 +3019,7 @@ public function testUpdateSubscriberFirstName() $this->assertEquals($result->subscriber->first_name, $firstName); // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); + $this->api->unsubscribe($result->subscriber->id); } /** @@ -3055,7 +3055,7 @@ public function testUpdateSubscriberEmailAddress() $this->assertEquals($result->subscriber->email_address, $newEmail); // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); + $this->api->unsubscribe($result->subscriber->id); } /** @@ -3093,7 +3093,7 @@ public function testUpdateSubscriberCustomFields() $this->assertEquals($result->subscriber->fields->last_name, $lastName); // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); + $this->api->unsubscribe($result->subscriber->id); } /** @@ -3173,7 +3173,7 @@ public function testUnsubscribeByID() ); // Unsubscribe. - $this->assertNull($this->api->unsubscribe_by_id($result->subscriber->id)); + $this->assertNull($this->api->unsubscribe($result->subscriber->id)); } /** @@ -3187,7 +3187,7 @@ public function testUnsubscribeByID() public function testUnsubscribeByIDWithInvalidSubscriberID() { $this->expectException(ClientException::class); - $subscriber = $this->api->unsubscribe_by_id(12345); + $subscriber = $this->api->unsubscribe(12345); } /** From 40716d72cfdd06a88824edf7fe3e178be7e7eeac Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 16:21:51 +0000 Subject: [PATCH 29/78] Update links to v4 API docs in comments --- src/ConvertKit_API.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 192bfc1..2b38e25 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -302,7 +302,7 @@ public function refresh_token(string $refreshToken, string $redirectURI) /** * Gets the current account * - * @see https://developers.convertkit.com/#account + * @see https://developers.convertkit.com/v4.html#get-current-account * * @return false|mixed */ @@ -390,7 +390,7 @@ public function get_growth_stats(\DateTime $starting = null, \DateTime $ending = * * @since 1.0.0 * - * @see https://developers.convertkit.com/#forms + * @see https://developers.convertkit.com/v4.html#convertkit-api-forms * * @return false|mixed */ @@ -404,7 +404,7 @@ public function get_forms() * * @since 1.0.0 * - * @see https://developers.convertkit.com/#forms + * @see https://developers.convertkit.com/v4.html#convertkit-api-forms * * @return false|mixed */ From d496d0f8a06ddd30e93bbc06ee4175a6951ee2da Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 16:23:43 +0000 Subject: [PATCH 30/78] Fix GitHub Action failing --- .github/workflows/tests.yml | 11 +++++++---- phpstan.neon.dist | 7 +------ phpstan.neon.example | 7 +------ 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 443dfc5..43d8299 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,10 +47,13 @@ jobs: path: .env.dist.testing contents: | - CONVERTKIT_API_KEY=${{ secrets.CONVERTKIT_API_KEY }} - CONVERTKIT_API_SECRET=${{ secrets.CONVERTKIT_API_SECRET }} - CONVERTKIT_API_KEY_NO_DATA=${{ secrets.CONVERTKIT_API_KEY_NO_DATA }} - CONVERTKIT_API_SECRET_NO_DATA=${{ secrets.CONVERTKIT_API_SECRET_NO_DATA }} + CONVERTKIT_OAUTH_ACCESS_TOKEN=${{ secrets.CONVERTKIT_OAUTH_ACCESS_TOKEN }} + CONVERTKIT_OAUTH_REFRESH_TOKEN=${{ secrets.CONVERTKIT_OAUTH_REFRESH_TOKEN }} + CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=${{ secrets.CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA }} + CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=${{ secrets.CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA }} + CONVERTKIT_OAUTH_CLIENT_ID=${{ secrets.CONVERTKIT_OAUTH_CLIENT_ID }} + CONVERTKIT_OAUTH_CLIENT_SECRET=${{ secrets.CONVERTKIT_OAUTH_CLIENT_SECRET }} + CONVERTKIT_OAUTH_REDIRECT_URI=${{ secrets.CONVERTKIT_OAUTH_REDIRECT_URI }} write-mode: append # Rename .env.dist.testing to .env, so PHPUnit reads it for tests. diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 5773b72..c3bd077 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -6,9 +6,4 @@ parameters: # Should not need to edit anything below here # Rule Level: https://phpstan.org/user-guide/rule-levels - level: 8 - - # Ignore the following errors, as PHPStan either does not have registered symbols for them yet, - # or the symbols are inaccurate. - ignoreErrors: - - '#\$headers of class GuzzleHttp\\Psr7\\Request constructor expects#' \ No newline at end of file + level: 8 \ No newline at end of file diff --git a/phpstan.neon.example b/phpstan.neon.example index 5773b72..c3bd077 100644 --- a/phpstan.neon.example +++ b/phpstan.neon.example @@ -6,9 +6,4 @@ parameters: # Should not need to edit anything below here # Rule Level: https://phpstan.org/user-guide/rule-levels - level: 8 - - # Ignore the following errors, as PHPStan either does not have registered symbols for them yet, - # or the symbols are inaccurate. - ignoreErrors: - - '#\$headers of class GuzzleHttp\\Psr7\\Request constructor expects#' \ No newline at end of file + level: 8 \ No newline at end of file From 6afae4be6e1acfb387986aeff7db98c4399e431f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 17:14:23 +0000 Subject: [PATCH 31/78] PHPStan compat. --- phpcs.xml | 2 +- src/ConvertKit_API.php | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index 290616f..9de2bbd 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -72,7 +72,7 @@ - + diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index d37937e..79726f6 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1945,8 +1945,8 @@ public function get(string $endpoint, array $args = []) /** * Performs a POST request to the API. * - * @param string $endpoint API Endpoint. - * @param array>> $args Request arguments. + * @param string $endpoint API Endpoint. + * @param array>> $args Request arguments. * * @return false|mixed */ @@ -1984,9 +1984,9 @@ public function delete(string $endpoint, array $args = []) /** * Performs an API request using Guzzle. * - * @param string $endpoint API Endpoint. - * @param string $method Request method. - * @param array>> $args Request arguments. + * @param string $endpoint API Endpoint. + * @param string $method Request method. + * @param array>> $args Request arguments. * * @throws \Exception If JSON encoding arguments failed. * From 53ab9adb58e25269eb7faa8dc4ac4e7a790a7267 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 26 Mar 2024 17:52:05 +0000 Subject: [PATCH 32/78] Fix named arguments in tests --- tests/ConvertKitAPITest.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index e477c88..e3aad82 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1062,7 +1062,7 @@ public function testAddSubscriberToSequence() { $result = $this->api->add_subscriber_to_sequence( sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); $this->assertInstanceOf('stdClass', $result); $this->assertArrayHasKey('subscriber', get_object_vars($result)); @@ -1086,7 +1086,7 @@ public function testAddSubscriberToSequenceWithInvalidSequenceID() $this->expectException(ClientException::class); $result = $this->api->add_subscriber_to_sequence( sequence_id: 12345, - email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); } @@ -1103,7 +1103,7 @@ public function testAddSubscriberToSequenceWithInvalidEmailAddress() $this->expectException(ClientException::class); $result = $this->api->add_subscriber_to_sequence( sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - email: 'not-an-email-address' + email_address: 'not-an-email-address' ); } @@ -1633,7 +1633,7 @@ public function testTagSubscriber() // Tag subscriber by email. $subscriber = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $emailAddress, + email_address: $emailAddress, ); $this->assertArrayHasKey('subscriber', get_object_vars($subscriber)); $this->assertArrayHasKey('id', get_object_vars($subscriber->subscriber)); @@ -1671,7 +1671,7 @@ public function testTagSubscriberWithInvalidTagID() $this->expectException(ClientException::class); $result = $this->api->tag_subscriber( tag_id: 12345, - email: $emailAddress + email_address: $emailAddress ); } @@ -1688,7 +1688,7 @@ public function testTagSubscriberWithInvalidEmailAddress() $this->expectException(ClientException::class); $result = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: 'not-an-email-address' + email_address: 'not-an-email-address' ); } @@ -1787,7 +1787,7 @@ public function testRemoveTagFromSubscriber() // Tag subscriber by email. $subscriber = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $emailAddress, + email_address: $emailAddress, ); // Remove tag from subscriber. @@ -1821,7 +1821,7 @@ public function testRemoveTagFromSubscriberWithInvalidTagID() // Tag subscriber by email. $subscriber = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $emailAddress, + email_address: $emailAddress, ); // Remove tag from subscriber. @@ -1867,7 +1867,7 @@ public function testRemoveTagFromSubscriberByEmail() // Tag subscriber by email. $subscriber = $this->api->tag_subscriber( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], - email: $emailAddress, + email_address: $emailAddress, ); // Remove tag from subscriber. @@ -1895,7 +1895,7 @@ public function testRemoveTagFromSubscriberByEmailWithInvalidTagID() $this->expectException(ClientException::class); $result = $this->api->remove_tag_from_subscriber_by_email( tag_id: 12345, - email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); } @@ -1912,7 +1912,7 @@ public function testRemoveTagFromSubscriberByEmailWithInvalidEmailAddress() $this->expectException(ClientException::class); $result = $this->api->remove_tag_from_subscriber_by_email( tag_id: $_ENV['CONVERTKIT_API_TAG_ID'], - email: 'not-an-email-address' + email_address: 'not-an-email-address' ); } @@ -2229,7 +2229,7 @@ public function testAddSubscriberToForm() { $result = $this->api->add_subscriber_to_form( form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'], - email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); $this->assertInstanceOf('stdClass', $result); $this->assertArrayHasKey('subscriber', get_object_vars($result)); @@ -2250,7 +2250,7 @@ public function testAddSubscriberToFormWithInvalidFormID() $this->expectException(ClientException::class); $result = $this->api->add_subscriber_to_form( form_id: 12345, - email: $this->generateEmailAddress() + email_address: $this->generateEmailAddress() ); } @@ -2267,7 +2267,7 @@ public function testAddSubscriberToFormWithInvalidEmailAddress() $this->expectException(ClientException::class); $result = $this->api->add_subscriber_to_form( form_id: $_ENV['CONVERTKIT_API_FORM_ID'], - email: 'not-an-email-address' + email_address: 'not-an-email-address' ); } From ce145dc4d60866416db2bb1f04d47822d5f9b873 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Mar 2024 12:54:24 +0000 Subject: [PATCH 33/78] Update tests to reflect changed method names --- tests/ConvertKitAPITest.php | 98 ++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index e3aad82..4c9fba7 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1052,15 +1052,15 @@ public function testGetSequencesPagination() } /** - * Test that add_subscriber_to_sequence() returns the expected data. + * Test that add_subscriber_to_sequence_by_email() returns the expected data. * * @since 1.0.0 * * @return void */ - public function testAddSubscriberToSequence() + public function testAddSubscriberToSequenceByEmail() { - $result = $this->api->add_subscriber_to_sequence( + $result = $this->api->add_subscriber_to_sequence_by_email( sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); @@ -1074,34 +1074,34 @@ public function testAddSubscriberToSequence() } /** - * Test that add_subscriber_to_sequence() throws a ClientException when an invalid + * Test that add_subscriber_to_sequence_by_email() throws a ClientException when an invalid * sequence is specified. * * @since 1.0.0 * * @return void */ - public function testAddSubscriberToSequenceWithInvalidSequenceID() + public function testAddSubscriberToSequenceByEmailWithInvalidSequenceID() { $this->expectException(ClientException::class); - $result = $this->api->add_subscriber_to_sequence( + $result = $this->api->add_subscriber_to_sequence_by_email( sequence_id: 12345, email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); } /** - * Test that add_subscriber_to_sequence() throws a ClientException when an invalid + * Test that add_subscriber_to_sequence_by_email() throws a ClientException when an invalid * email address is specified. * * @since 1.0.0 * * @return void */ - public function testAddSubscriberToSequenceWithInvalidEmailAddress() + public function testAddSubscriberToSequenceByEmailWithInvalidEmailAddress() { $this->expectException(ClientException::class); - $result = $this->api->add_subscriber_to_sequence( + $result = $this->api->add_subscriber_to_sequence_by_email( sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], email_address: 'not-an-email-address' ); @@ -1114,7 +1114,7 @@ public function testAddSubscriberToSequenceWithInvalidEmailAddress() * * @return void */ - public function testAddSubscriberToSequenceByID() + public function testAddSubscriberToSequence() { $result = $this->api->add_subscriber_to_sequence( sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], @@ -1134,7 +1134,7 @@ public function testAddSubscriberToSequenceByID() * * @return void */ - public function testAddSubscriberToSequenceByIDWithInvalidSequenceID() + public function testAddSubscriberToSequenceWithInvalidSequenceID() { $this->expectException(ClientException::class); $result = $this->api->add_subscriber_to_sequence( @@ -1151,7 +1151,7 @@ public function testAddSubscriberToSequenceByIDWithInvalidSequenceID() * * @return void */ - public function testAddSubscriberToSequenceByIDWithInvalidSubscriberID() + public function testAddSubscriberToSequenceWithInvalidSubscriberID() { $this->expectException(ClientException::class); $result = $this->api->add_subscriber_to_sequence( @@ -1616,13 +1616,13 @@ public function testCreateTagsThatExist() } /** - * Test that tag_subscriber() returns the expected data. + * Test that tag_subscriber_by_email() returns the expected data. * * @since 1.0.0 * * @return void */ - public function testTagSubscriber() + public function testTagSubscriberByEmail() { // Create subscriber. $emailAddress = $this->generateEmailAddress(); @@ -1631,7 +1631,7 @@ public function testTagSubscriber() ); // Tag subscriber by email. - $subscriber = $this->api->tag_subscriber( + $subscriber = $this->api->tag_subscriber_by_email( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], email_address: $emailAddress, ); @@ -1653,14 +1653,14 @@ public function testTagSubscriber() } /** - * Test that tag_subscriber() throws a ClientException when an invalid + * Test that tag_subscriber_by_email() throws a ClientException when an invalid * tag is specified. * * @since 2.0.0 * * @return void */ - public function testTagSubscriberWithInvalidTagID() + public function testTagSubscriberByEmailWithInvalidTagID() { // Create subscriber. $emailAddress = $this->generateEmailAddress(); @@ -1669,24 +1669,24 @@ public function testTagSubscriberWithInvalidTagID() ); $this->expectException(ClientException::class); - $result = $this->api->tag_subscriber( + $result = $this->api->tag_subscriber_by_email( tag_id: 12345, email_address: $emailAddress ); } /** - * Test that tag_subscriber() throws a ClientException when an invalid + * Test that tag_subscriber_by_email() throws a ClientException when an invalid * email address is specified. * * @since 2.0.0 * * @return void */ - public function testTagSubscriberWithInvalidEmailAddress() + public function testTagSubscriberByEmailWithInvalidEmailAddress() { $this->expectException(ClientException::class); - $result = $this->api->tag_subscriber( + $result = $this->api->tag_subscriber_by_email( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], email_address: 'not-an-email-address' ); @@ -1699,7 +1699,7 @@ public function testTagSubscriberWithInvalidEmailAddress() * * @return void */ - public function testTagSubscriberByID() + public function testTagSubscriber() { // Create subscriber. $emailAddress = $this->generateEmailAddress(); @@ -1737,7 +1737,7 @@ public function testTagSubscriberByID() * * @return void */ - public function testTagSubscriberByIDWithInvalidTagID() + public function testTagSubscriberWithInvalidTagID() { // Create subscriber. $emailAddress = $this->generateEmailAddress(); @@ -1760,7 +1760,7 @@ public function testTagSubscriberByIDWithInvalidTagID() * * @return void */ - public function testTagSubscriberByIDWithInvalidSubscriberID() + public function testTagSubscriberWithInvalidSubscriberID() { $this->expectException(ClientException::class); $result = $this->api->tag_subscriber( @@ -1785,7 +1785,7 @@ public function testRemoveTagFromSubscriber() ); // Tag subscriber by email. - $subscriber = $this->api->tag_subscriber( + $subscriber = $this->api->tag_subscriber_by_email( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], email_address: $emailAddress, ); @@ -1819,7 +1819,7 @@ public function testRemoveTagFromSubscriberWithInvalidTagID() ); // Tag subscriber by email. - $subscriber = $this->api->tag_subscriber( + $subscriber = $this->api->tag_subscriber_by_email( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], email_address: $emailAddress, ); @@ -1865,7 +1865,7 @@ public function testRemoveTagFromSubscriberByEmail() ); // Tag subscriber by email. - $subscriber = $this->api->tag_subscriber( + $subscriber = $this->api->tag_subscriber_by_email( tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], email_address: $emailAddress, ); @@ -2219,15 +2219,15 @@ public function testGetResourcesInvalidResourceType() } /** - * Test that add_subscriber_to_form() returns the expected data. + * Test that add_subscriber_to_form_by_email() returns the expected data. * * @since 1.0.0 * * @return void */ - public function testAddSubscriberToForm() + public function testAddSubscriberToFormByEmail() { - $result = $this->api->add_subscriber_to_form( + $result = $this->api->add_subscriber_to_form_by_email( form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'], email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); @@ -2238,17 +2238,17 @@ public function testAddSubscriberToForm() } /** - * Test that add_subscriber_to_form() throws a ClientException when an invalid + * Test that add_subscriber_to_form_by_email() throws a ClientException when an invalid * form ID is specified. * * @since 1.0.0 * * @return void */ - public function testAddSubscriberToFormWithInvalidFormID() + public function testAddSubscriberToFormByEmailWithInvalidFormID() { $this->expectException(ClientException::class); - $result = $this->api->add_subscriber_to_form( + $result = $this->api->add_subscriber_to_form_by_email( form_id: 12345, email_address: $this->generateEmailAddress() ); @@ -2262,10 +2262,10 @@ public function testAddSubscriberToFormWithInvalidFormID() * * @return void */ - public function testAddSubscriberToFormWithInvalidEmailAddress() + public function testAddSubscriberToFormByEmailWithInvalidEmailAddress() { $this->expectException(ClientException::class); - $result = $this->api->add_subscriber_to_form( + $result = $this->api->add_subscriber_to_form_by_email( form_id: $_ENV['CONVERTKIT_API_FORM_ID'], email_address: 'not-an-email-address' ); @@ -2278,7 +2278,7 @@ public function testAddSubscriberToFormWithInvalidEmailAddress() * * @return void */ - public function testAddSubscriberToFormByID() + public function testAddSubscriberToForm() { $result = $this->api->add_subscriber_to_form( form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'], @@ -2298,7 +2298,7 @@ public function testAddSubscriberToFormByID() * * @return void */ - public function testAddSubscriberToFormByIDWithInvalidFormID() + public function testAddSubscriberToFormWithInvalidFormID() { $this->expectException(ClientException::class); $result = $this->api->add_subscriber_to_form( @@ -2315,7 +2315,7 @@ public function testAddSubscriberToFormByIDWithInvalidFormID() * * @return void */ - public function testAddSubscriberToFormByIDWithInvalidSubscriberID() + public function testAddSubscriberToFormWithInvalidSubscriberID() { $this->expectException(ClientException::class); $result = $this->api->add_subscriber_to_form( @@ -3111,13 +3111,13 @@ public function testUpdateSubscriberWithInvalidSubscriberID() } /** - * Test that unsubscribe() works with a valid subscriber email address. + * Test that unsubscribe_by_email() works with a valid subscriber email address. * * @since 1.0.0 * * @return void */ - public function testUnsubscribe() + public function testUnsubscribeByEmail() { // Add a subscriber. $emailAddress = $this->generateEmailAddress(); @@ -3126,35 +3126,35 @@ public function testUnsubscribe() ); // Unsubscribe. - $this->assertNull($this->api->unsubscribe($emailAddress)); + $this->assertNull($this->api->unsubscribe_by_email($emailAddress)); } /** - * Test that unsubscribe() throws a ClientException when an email + * Test that unsubscribe_by_email() throws a ClientException when an email * address is specified that is not subscribed. * * @since 1.0.0 * * @return void */ - public function testUnsubscribeWithNotSubscribedEmailAddress() + public function testUnsubscribeByEmailWithNotSubscribedEmailAddress() { $this->expectException(ClientException::class); - $subscriber = $this->api->unsubscribe('not-subscribed@convertkit.com'); + $subscriber = $this->api->unsubscribe_by_email('not-subscribed@convertkit.com'); } /** - * Test that unsubscribe() throws a ClientException when an invalid + * Test that unsubscribe_by_email() throws a ClientException when an invalid * email address is specified. * * @since 1.0.0 * * @return void */ - public function testUnsubscribeWithInvalidEmailAddress() + public function testUnsubscribeByEmailWithInvalidEmailAddress() { $this->expectException(ClientException::class); - $subscriber = $this->api->unsubscribe('invalid-email'); + $subscriber = $this->api->unsubscribe_by_email('invalid-email'); } /** @@ -3164,7 +3164,7 @@ public function testUnsubscribeWithInvalidEmailAddress() * * @return void */ - public function testUnsubscribeByID() + public function testUnsubscribe() { // Add a subscriber. $emailAddress = $this->generateEmailAddress(); @@ -3184,7 +3184,7 @@ public function testUnsubscribeByID() * * @return void */ - public function testUnsubscribeByIDWithInvalidSubscriberID() + public function testUnsubscribeWithInvalidSubscriberID() { $this->expectException(ClientException::class); $subscriber = $this->api->unsubscribe(12345); From 63cf3284a496d89430dcae664cf12892645b8fcb Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Mar 2024 13:50:58 +0000 Subject: [PATCH 34/78] Add `tearDown` method to cleanup account data on test pass/fail --- tests/ConvertKitAPITest.php | 127 ++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 48 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index b049d03..d72ad31 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -33,6 +33,24 @@ class ConvertKitAPITest extends TestCase */ protected $logFile = ''; + /** + * Custom Field IDs to delete on teardown of a test. + * + * @since 2.0.0 + * + * @var array + */ + protected $custom_field_ids = []; + + /** + * Subscriber IDs to unsubscribe on teardown of a test. + * + * @since 2.0.0 + * + * @var array + */ + protected $subscriber_ids = []; + /** * Load .env configuration into $_ENV superglobal, and initialize the API * class before each test. @@ -61,6 +79,26 @@ protected function setUp(): void ); } + /** + * Cleanup data from the ConvertKit account on a test pass/fail, such as unsubscribing, deleting custom fields etc + * + * @since 2.0.0 + * + * @return void + */ + protected function tearDown(): void + { + // Delete any Custom Fields. + foreach($this->custom_field_ids as $id) { + $this->api->delete_custom_field($id); + } + + // Unsubscribe any Subscribers. + foreach($this->subscriber_ids as $id) { + $this->api->unsubscribe_by_id($id); + } + } + /** * Test that a Response instance is returned when calling getResponseInterface() * after making an API request. @@ -2683,11 +2721,11 @@ public function testCreateSubscriber() email_address: $emailAddress ); - // Assert subscriber exists with correct data. - $this->assertEquals($result->subscriber->email_address, $emailAddress); + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $result->subscriber->id; - // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); + // Assert subscriber exists with correct data. + $this->assertEquals($result->subscriber->email_address, $emailAddress); } /** @@ -2707,12 +2745,12 @@ public function testCreateSubscriberWithFirstName() first_name: $firstName ); + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $result->subscriber->id; + // Assert subscriber exists with correct data. $this->assertEquals($result->subscriber->email_address, $emailAddress); $this->assertEquals($result->subscriber->first_name, $firstName); - - // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2732,12 +2770,12 @@ public function testCreateSubscriberWithSubscriberState() subscriber_state: $subscriberState ); + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $result->subscriber->id; + // Assert subscriber exists with correct data. $this->assertEquals($result->subscriber->email_address, $emailAddress); $this->assertEquals($result->subscriber->state, $subscriberState); - - // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2759,12 +2797,12 @@ public function testCreateSubscriberWithCustomFields() ] ); + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $result->subscriber->id; + // Assert subscriber exists with correct data. $this->assertEquals($result->subscriber->email_address, $emailAddress); $this->assertEquals($result->subscriber->fields->last_name, $lastName); - - // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2820,11 +2858,11 @@ public function testCreateSubscriberWithInvalidCustomFields() ] ); + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $result->subscriber->id; + // Assert subscriber exists with correct data. $this->assertEquals($result->subscriber->email_address, $emailAddress); - - // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -2846,15 +2884,17 @@ public function testCreateSubscribers() ]; $result = $this->api->create_subscribers($subscribers); + // Set subscriber_id to ensure subscriber is unsubscribed after test. + foreach ($result->subscribers as $i => $subscriber) { + $this->subscriber_ids[] = $subscriber->id; + } + // Assert no failures. $this->assertCount(0, $result->failures); // Assert subscribers exists with correct data. foreach ($result->subscribers as $i => $subscriber) { $this->assertEquals($subscriber->email_address, $subscribers[$i]['email_address']); - - // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($subscriber->id); } } @@ -3002,6 +3042,9 @@ public function testUpdateSubscriberFirstName() email_address: $emailAddress ); + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $result->subscriber->id; + // Assert subscriber created with no first name. $this->assertNull($result->subscriber->first_name); @@ -3017,9 +3060,6 @@ public function testUpdateSubscriberFirstName() // Assert changes were made. $this->assertEquals($result->subscriber->id, $subscriberID); $this->assertEquals($result->subscriber->first_name, $firstName); - - // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -3037,6 +3077,9 @@ public function testUpdateSubscriberEmailAddress() email_address: $emailAddress ); + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $result->subscriber->id; + // Assert subscriber created. $this->assertEquals($result->subscriber->email_address, $emailAddress); @@ -3053,9 +3096,6 @@ public function testUpdateSubscriberEmailAddress() // Assert changes were made. $this->assertEquals($result->subscriber->id, $subscriberID); $this->assertEquals($result->subscriber->email_address, $newEmail); - - // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -3074,6 +3114,9 @@ public function testUpdateSubscriberCustomFields() email_address: $emailAddress ); + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $result->subscriber->id; + // Assert subscriber created. $this->assertEquals($result->subscriber->email_address, $emailAddress); @@ -3091,9 +3134,6 @@ public function testUpdateSubscriberCustomFields() // Assert changes were made. $this->assertEquals($result->subscriber->id, $subscriberID); $this->assertEquals($result->subscriber->fields->last_name, $lastName); - - // Unsubscribe to cleanup test. - $this->api->unsubscribe_by_id($result->subscriber->id); } /** @@ -3691,15 +3731,15 @@ public function testCreateCustomField() $label = 'Custom Field ' . mt_rand(); $result = $this->api->create_custom_field($label); + // Set custom_field_ids to ensure custom fields are deleted after test. + $this->custom_field_ids[] = $result->custom_field->id; + $result = get_object_vars($result->custom_field); $this->assertArrayHasKey('id', $result); $this->assertArrayHasKey('name', $result); $this->assertArrayHasKey('key', $result); $this->assertArrayHasKey('label', $result); $this->assertEquals($result['label'], $label); - - // Delete custom field. - $this->api->delete_custom_field($result['id']); } /** @@ -3731,25 +3771,16 @@ public function testCreateCustomFields() ]; $result = $this->api->create_custom_fields($labels); + // Set custom_field_ids to ensure custom fields are deleted after test. + foreach ($result->custom_fields as $index => $customField) { + $this->custom_field_ids[] = $customField->id; + } + // Assert no failures. $this->assertCount(0, $result->failures); // Confirm result is an array comprising of each custom field that was created. $this->assertIsArray($result->custom_fields); - foreach ($result->custom_fields as $index => $customField) { - // Confirm individual custom field. - $customField = get_object_vars($customField); - $this->assertArrayHasKey('id', $customField); - $this->assertArrayHasKey('name', $customField); - $this->assertArrayHasKey('key', $customField); - $this->assertArrayHasKey('label', $customField); - - // Confirm label is correct. - $this->assertEquals($labels[$index], $customField['label']); - - // Delete custom field as tests passed. - $this->api->delete_custom_field($customField['id']); - } } /** @@ -3766,6 +3797,9 @@ public function testUpdateCustomField() $result = $this->api->create_custom_field($label); $id = $result->custom_field->id; + // Set custom_field_ids to ensure custom fields are deleted after test. + $this->custom_field_ids[] = $result->custom_field->id; + // Change label. $newLabel = 'Custom Field ' . mt_rand(); $this->api->update_custom_field($id, $newLabel); @@ -3777,9 +3811,6 @@ public function testUpdateCustomField() $this->assertEquals($customField->label, $newLabel); } } - - // Delete custom field as tests passed. - $this->api->delete_custom_field($id); } /** From c4a48b0adad355d4bd19c840c8cc5bb39a748c32 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Mar 2024 13:53:38 +0000 Subject: [PATCH 35/78] Coding standards --- tests/ConvertKitAPITest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index d72ad31..cd9af5f 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -35,18 +35,18 @@ class ConvertKitAPITest extends TestCase /** * Custom Field IDs to delete on teardown of a test. - * + * * @since 2.0.0 - * + * * @var array */ protected $custom_field_ids = []; /** * Subscriber IDs to unsubscribe on teardown of a test. - * + * * @since 2.0.0 - * + * * @var array */ protected $subscriber_ids = []; @@ -89,12 +89,12 @@ protected function setUp(): void protected function tearDown(): void { // Delete any Custom Fields. - foreach($this->custom_field_ids as $id) { + foreach ($this->custom_field_ids as $id) { $this->api->delete_custom_field($id); } // Unsubscribe any Subscribers. - foreach($this->subscriber_ids as $id) { + foreach ($this->subscriber_ids as $id) { $this->api->unsubscribe_by_id($id); } } @@ -2725,7 +2725,7 @@ public function testCreateSubscriber() $this->subscriber_ids[] = $result->subscriber->id; // Assert subscriber exists with correct data. - $this->assertEquals($result->subscriber->email_address, $emailAddress); + $this->assertEquals($result->subscriber->email_address, $emailAddress); } /** From 683108d287800f9322afd1575f1158d9ced1305b Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Mar 2024 13:57:16 +0000 Subject: [PATCH 36/78] Remove test assertions for `has_previous_page` at end of each pagination test --- tests/ConvertKitAPITest.php | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index cd9af5f..0d76c6d 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -949,10 +949,6 @@ public function testGetFormSubscriptionsPagination() // Assert a single subscriber was returned. $this->assertCount(1, $result->subscribers); - - // Assert has_previous_page and has_next_page are correct. - $this->assertFalse($result->pagination->has_previous_page); - $this->assertTrue($result->pagination->has_next_page); } /** @@ -1083,10 +1079,6 @@ public function testGetSequencesPagination() // Assert a single sequence was returned. $this->assertCount(1, $result->sequences); - - // Assert has_previous_page and has_next_page are correct. - $this->assertFalse($result->pagination->has_previous_page); - $this->assertTrue($result->pagination->has_next_page); } /** @@ -1410,10 +1402,6 @@ public function testGetSequenceSubscriptionsPagination() // Assert a single subscriber was returned. $this->assertCount(1, $result->subscribers); - - // Assert has_previous_page and has_next_page are correct. - $this->assertFalse($result->pagination->has_previous_page); - $this->assertTrue($result->pagination->has_next_page); } /** @@ -1542,10 +1530,6 @@ public function testGetTagsPagination() // Assert a single subscriber was returned. $this->assertCount(1, $result->tags); - - // Assert has_previous_page and has_next_page are correct. - $this->assertFalse($result->pagination->has_previous_page); - $this->assertTrue($result->pagination->has_next_page); } /** @@ -2168,10 +2152,6 @@ public function testGetTagSubscriptionsPagination() // Assert a single subscriber was returned. $this->assertCount(1, $result->subscribers); - - // Assert has_previous_page and has_next_page are correct. - $this->assertFalse($result->pagination->has_previous_page); - $this->assertTrue($result->pagination->has_next_page); } /** @@ -2621,10 +2601,6 @@ public function testGetSubscribersPagination() // Assert a single subscriber was returned. $this->assertCount(1, $result->subscribers); - - // Assert has_previous_page and has_next_page are correct. - $this->assertFalse($result->pagination->has_previous_page); - $this->assertTrue($result->pagination->has_next_page); } /** @@ -3318,10 +3294,6 @@ public function testGetSubscriberTagsPagination() // Assert a single tag was returned. $this->assertCount(1, $result->tags); - - // Assert has_previous_page and has_next_page are correct. - $this->assertFalse($result->pagination->has_previous_page); - $this->assertTrue($result->pagination->has_next_page); } /** @@ -3713,10 +3685,6 @@ public function testGetCustomFieldsPagination() // Assert a single custom field was returned. $this->assertCount(1, $result->custom_fields); - - // Assert has_previous_page and has_next_page are correct. - $this->assertFalse($result->pagination->has_previous_page); - $this->assertTrue($result->pagination->has_next_page); } /** From a0000638ef484f161db2b745dbac4f292241c117 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Mar 2024 14:00:15 +0000 Subject: [PATCH 37/78] Remove `assertCount` at end of pagination tests, as again tests in parallel may change the subscriber count --- tests/ConvertKitAPITest.php | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 0d76c6d..1e02c1b 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -946,9 +946,6 @@ public function testGetFormSubscriptionsPagination() // Assert subscribers and pagination exist. $this->assertDataExists($result, 'subscribers'); $this->assertPaginationExists($result); - - // Assert a single subscriber was returned. - $this->assertCount(1, $result->subscribers); } /** @@ -1399,9 +1396,6 @@ public function testGetSequenceSubscriptionsPagination() // Assert subscribers and pagination exist. $this->assertDataExists($result, 'subscribers'); $this->assertPaginationExists($result); - - // Assert a single subscriber was returned. - $this->assertCount(1, $result->subscribers); } /** @@ -1527,9 +1521,6 @@ public function testGetTagsPagination() // Assert tags and pagination exist. $this->assertDataExists($result, 'tags'); $this->assertPaginationExists($result); - - // Assert a single subscriber was returned. - $this->assertCount(1, $result->tags); } /** @@ -2149,9 +2140,6 @@ public function testGetTagSubscriptionsPagination() // Assert subscribers and pagination exist. $this->assertDataExists($result, 'subscribers'); $this->assertPaginationExists($result); - - // Assert a single subscriber was returned. - $this->assertCount(1, $result->subscribers); } /** @@ -2598,9 +2586,6 @@ public function testGetSubscribersPagination() // Assert subscribers and pagination exist. $this->assertDataExists($result, 'subscribers'); $this->assertPaginationExists($result); - - // Assert a single subscriber was returned. - $this->assertCount(1, $result->subscribers); } /** From 8b7817fe34ad1b020fb05fce0b6a14d443353d91 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Mar 2024 14:43:40 +0000 Subject: [PATCH 38/78] Added `get_segments` method --- src/ConvertKit_API.php | 38 ++++++++++++++ tests/ConvertKitAPITest.php | 99 +++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 881ed4b..05157d9 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1700,6 +1700,44 @@ public function create_purchase(array $options) return $this->post('purchases', $options); } + /** + * List segments. + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 2.0.0 + * + * @see https://developers.convertkit.com/v4.html#convertkit-api-segments + * + * @return false|mixed + */ + public function get_segments( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Build parameters. + $options = ['include_total_count' => $include_total_count]; + + // Build pagination parameters. + $options = $this->build_pagination_params( + params: $options, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ); + + // Send request. + return $this->get( + endpoint: 'segments', + args: $options + ); + } + /** * Get markup from ConvertKit for the provided $url. * diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index b049d03..ef79dbc 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -3962,6 +3962,105 @@ public function testCreatePurchaseWithMissingData() ]); } + /** + * Test that get_segments() returns the expected data. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSegments() + { + $result = $this->api->get_segments(); + + // Assert segments and pagination exist. + $this->assertDataExists($result, 'segments'); + $this->assertPaginationExists($result); + } + + /** + * Test that get_segments() returns the expected data + * when the total count is included. + * + * @since 1.0.0 + * + * @return void + */ + public function testGetSegmentsWithTotalCount() + { + $result = $this->api->get_segments( + include_total_count: true + ); + + // Assert segments and pagination exist. + $this->assertDataExists($result, 'segments'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + + /** + * Test that get_segments() returns the expected data + * when pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSegmentsPagination() + { + $result = $this->api->get_segments( + per_page: 1 + ); + + // Assert segments and pagination exist. + $this->assertDataExists($result, 'segments'); + $this->assertPaginationExists($result); + + // Assert a single segment was returned. + $this->assertCount(1, $result->segments); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->get_segments( + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert segments and pagination exist. + $this->assertDataExists($result, 'segments'); + $this->assertPaginationExists($result); + + // Assert a single segment was returned. + $this->assertCount(1, $result->segments); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_segments( + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert segments and pagination exist. + $this->assertDataExists($result, 'segments'); + $this->assertPaginationExists($result); + + // Assert a single segment was returned. + $this->assertCount(1, $result->segments); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + } + /** * Test that fetching a legacy form's markup works. * From a8e809bb4de27b589e992496ff49802c54894113 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Mar 2024 15:00:46 +0000 Subject: [PATCH 39/78] Change `unsubscribe_by_id` to `unsubscribe` in test `tearDown` --- tests/ConvertKitAPITest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 92dc7e0..2b7e524 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -95,7 +95,7 @@ protected function tearDown(): void // Unsubscribe any Subscribers. foreach ($this->subscriber_ids as $id) { - $this->api->unsubscribe_by_id($id); + $this->api->unsubscribe($id); } } From 9ce7da4541f924d9d58138d4fcb10f791dcce1eb Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 27 Mar 2024 15:25:27 +0000 Subject: [PATCH 40/78] Add `get_email_templates` method --- src/ConvertKit_API.php | 38 ++++++++++++++ tests/ConvertKitAPITest.php | 99 +++++++++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 881ed4b..ce661b1 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -839,6 +839,44 @@ public function get_tag_subscriptions( ); } + /** + * List email templates. + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 2.0.0 + * + * @see https://developers.convertkit.com/v4.html#convertkit-api-email-templates + * + * @return false|mixed + */ + public function get_email_templates( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Build parameters. + $options = ['include_total_count' => $include_total_count]; + + // Build pagination parameters. + $options = $this->build_pagination_params( + params: $options, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ); + + // Send request. + return $this->get( + endpoint: 'email_templates', + args: $options + ); + } + /** * Gets a resource index * Possible resources: forms, landing_pages, subscription_forms, tags diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 1e02c1b..241ed50 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -3281,6 +3281,105 @@ public function testGetSubscriberTagsPagination() $this->assertCount(1, $result->tags); } + /** + * Test that get_email_templates() returns the expected data. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetEmailTemplates() + { + $result = $this->api->get_email_templates(); + + // Assert email templates and pagination exist. + $this->assertDataExists($result, 'email_templates'); + $this->assertPaginationExists($result); + } + + /** + * Test that get_email_templates() returns the expected data + * when the total count is included. + * + * @since 1.0.0 + * + * @return void + */ + public function testGetEmailTemplatesWithTotalCount() + { + $result = $this->api->get_email_templates( + include_total_count: true + ); + + // Assert email templates and pagination exist. + $this->assertDataExists($result, 'email_templates'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + + /** + * Test that get_email_templates() returns the expected data + * when pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetEmailTemplatesPagination() + { + $result = $this->api->get_email_templates( + per_page: 1 + ); + + // Assert email templates and pagination exist. + $this->assertDataExists($result, 'email_templates'); + $this->assertPaginationExists($result); + + // Assert a single email template was returned. + $this->assertCount(1, $result->email_templates); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->get_email_templates( + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert email templates and pagination exist. + $this->assertDataExists($result, 'email_templates'); + $this->assertPaginationExists($result); + + // Assert a single email template was returned. + $this->assertCount(1, $result->email_templates); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_email_templates( + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert email templates and pagination exist. + $this->assertDataExists($result, 'email_templates'); + $this->assertPaginationExists($result); + + // Assert a single email template was returned. + $this->assertCount(1, $result->email_templates); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + } + /** * Test that create_broadcast(), update_broadcast() and destroy_broadcast() works * when specifying valid published_at and send_at values. From b64d07d2368f338e379f9ba68694cb2a36ef1c1c Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 28 Mar 2024 15:13:46 +0000 Subject: [PATCH 41/78] Updated methods and tests --- src/ConvertKit_API.php | 191 ++++++++++++++++++++++-------------- tests/ConvertKitAPITest.php | 66 ++++++------- 2 files changed, 147 insertions(+), 110 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index cc39343..d6fb36b 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1283,39 +1283,65 @@ public function get_subscriber_tags( } /** - * Gets a list of broadcasts. + * List broadcasts. * - * @see https://developers.convertkit.com/#list-broadcasts + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * - * @return false|array + * @see https://developers.convertkit.com/v4.html#list-broadcasts + * + * @return false|mixed */ - public function get_broadcasts() - { - return $this->get('broadcasts'); + public function get_broadcasts( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Build parameters. + $options = ['include_total_count' => $include_total_count]; + + // Build pagination parameters. + $options = $this->build_pagination_params( + params: $options, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ); + + // Send request. + return $this->get( + endpoint: 'broadcasts', + args: $options + ); } /** * Creates a broadcast. * - * @param string $subject The broadcast email's subject. - * @param string $content The broadcast's email HTML content. - * @param string $description An internal description of this broadcast. - * @param boolean $public Specifies whether or not this is a public post. - * @param \DateTime $published_at Specifies the time that this post was published (applicable - * only to public posts). - * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create - * a draft broadcast. If set to a future time, this is the time that - * the broadcast will be scheduled to send. - * @param string $email_address Sending email address; leave blank to use your account's - * default sending email address. - * @param string $email_layout_template Name of the email template to use; leave blank to use your - * account's default email template. - * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image - * (applicable only to public posts). - * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast - * post (applicable only to public posts). - * - * @see https://developers.convertkit.com/#create-a-broadcast + * @param string $subject The broadcast email's subject. + * @param string $content The broadcast's email HTML content. + * @param string $description An internal description of this broadcast. + * @param boolean $public Specifies whether or not this is a public post. + * @param \DateTime $published_at Specifies the time that this post was published (applicable + * only to public posts). + * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create + * a draft broadcast. If set to a future time, this is the time that + * the broadcast will be scheduled to send. + * @param string $email_address Sending email address; leave blank to use your account's + * default sending email address. + * @param string $email_template_id ID of the email template to use; leave blank to use your + * account's default email template. + * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image + * (applicable only to public posts). + * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast + * post (applicable only to public posts). + * @param string $preview_text Specify the preview text of the email. + * @param array $subscriber_filter Filter subscriber(s) to send the email to. + * + * @see https://developers.convertkit.com/v4.html#create-a-broadcast * * @return false|object */ @@ -1327,22 +1353,28 @@ public function create_broadcast( \DateTime $published_at = null, \DateTime $send_at = null, string $email_address = '', - string $email_layout_template = '', + string $email_template_id = '', string $thumbnail_alt = '', - string $thumbnail_url = '' + string $thumbnail_url = '', + string $preview_text = '', + array $subscriber_filter = [] ) { $options = [ - 'content' => $content, - 'description' => $description, - 'email_address' => $email_address, - 'email_layout_template' => $email_layout_template, - 'public' => $public, - 'published_at' => (!is_null($published_at) ? $published_at->format('Y-m-d H:i:s') : ''), - 'send_at' => (!is_null($send_at) ? $send_at->format('Y-m-d H:i:s') : ''), - 'subject' => $subject, - 'thumbnail_alt' => $thumbnail_alt, - 'thumbnail_url' => $thumbnail_url, + 'email_template_id' => $email_template_id, + 'email_address' => $email_address, + 'content' => $content, + 'description' => $description, + 'public' => $public, + 'published_at' => (!is_null($published_at) ? $published_at->format('Y-m-d H:i:s') : ''), + 'send_at' => (!is_null($send_at) ? $send_at->format('Y-m-d H:i:s') : ''), + 'thumbnail_alt' => $thumbnail_alt, + 'thumbnail_url' => $thumbnail_url, + 'preview_text' => $preview_text, + 'subject' => $subject, ]; + if (count($subscriber_filter)) { + $options['subscriber_filter'] = $subscriber_filter; + } // Iterate through options, removing blank entries. foreach ($options as $key => $value) { @@ -1357,7 +1389,10 @@ public function create_broadcast( } // Send request. - return $this->post('broadcasts', $options); + return $this->post( + endpoint: 'broadcasts', + args: $options + ); } /** @@ -1365,7 +1400,7 @@ public function create_broadcast( * * @param integer $id Broadcast ID. * - * @see https://developers.convertkit.com/#retrieve-a-specific-broadcast + * @see https://developers.convertkit.com/v4.html#get-a-broadcast * * @return false|object */ @@ -1380,7 +1415,7 @@ public function get_broadcast(int $id) * * @param integer $id Broadcast ID. * - * @see https://developers.convertkit.com/#retrieve-a-specific-broadcast + * @see https://developers.convertkit.com/v4.html#get-stats * * @return false|object */ @@ -1392,24 +1427,26 @@ public function get_broadcast_stats(int $id) /** * Updates a broadcast. * - * @param integer $id Broadcast ID. - * @param string $subject The broadcast email's subject. - * @param string $content The broadcast's email HTML content. - * @param string $description An internal description of this broadcast. - * @param boolean $public Specifies whether or not this is a public post. - * @param \DateTime $published_at Specifies the time that this post was published (applicable - * only to public posts). - * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create - * a draft broadcast. If set to a future time, this is the time that - * the broadcast will be scheduled to send. - * @param string $email_address Sending email address; leave blank to use your account's - * default sending email address. - * @param string $email_layout_template Name of the email template to use; leave blank to use your - * account's default email template. - * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image - * (applicable only to public posts). - * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast - * post (applicable only to public posts). + * @param integer $id Broadcast ID. + * @param string $subject The broadcast email's subject. + * @param string $content The broadcast's email HTML content. + * @param string $description An internal description of this broadcast. + * @param boolean $public Specifies whether or not this is a public post. + * @param \DateTime $published_at Specifies the time that this post was published (applicable + * only to public posts). + * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create + * a draft broadcast. If set to a future time, this is the time that + * the broadcast will be scheduled to send. + * @param string $email_address Sending email address; leave blank to use your account's + * default sending email address. + * @param string $email_template_id ID of the email template to use; leave blank to use your + * account's default email template. + * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image + * (applicable only to public posts). + * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast + * post (applicable only to public posts). + * @param string $preview_text Specify the preview text of the email. + * @param array $subscriber_filter Filter subscriber(s) to send the email to. * * @see https://developers.convertkit.com/#create-a-broadcast * @@ -1424,22 +1461,28 @@ public function update_broadcast( \DateTime $published_at = null, \DateTime $send_at = null, string $email_address = '', - string $email_layout_template = '', + string $email_template_id = '', string $thumbnail_alt = '', - string $thumbnail_url = '' + string $thumbnail_url = '', + string $preview_text = '', + array $subscriber_filter = [] ) { $options = [ - 'content' => $content, - 'description' => $description, - 'email_address' => $email_address, - 'email_layout_template' => $email_layout_template, - 'public' => $public, - 'published_at' => (!is_null($published_at) ? $published_at->format('Y-m-d H:i:s') : ''), - 'send_at' => (!is_null($send_at) ? $send_at->format('Y-m-d H:i:s') : ''), - 'subject' => $subject, - 'thumbnail_alt' => $thumbnail_alt, - 'thumbnail_url' => $thumbnail_url, + 'email_template_id' => $email_template_id, + 'email_address' => $email_address, + 'content' => $content, + 'description' => $description, + 'public' => $public, + 'published_at' => (!is_null($published_at) ? $published_at->format('Y-m-d H:i:s') : ''), + 'send_at' => (!is_null($send_at) ? $send_at->format('Y-m-d H:i:s') : ''), + 'thumbnail_alt' => $thumbnail_alt, + 'thumbnail_url' => $thumbnail_url, + 'preview_text' => $preview_text, + 'subject' => $subject, ]; + if (count($subscriber_filter)) { + $options['subscriber_filter'] = $subscriber_filter; + } // Iterate through options, removing blank entries. foreach ($options as $key => $value) { @@ -1455,8 +1498,8 @@ public function update_broadcast( // Send request. return $this->put( - sprintf('broadcasts/%s', $id), - $options + endpoint: sprintf('broadcasts/%s', $id), + args: $options ); } @@ -1467,11 +1510,11 @@ public function update_broadcast( * * @since 1.0.0 * - * @see https://developers.convertkit.com/#destroy-webhook + * @see https://developers.convertkit.com/v4.html#delete-a-broadcast * * @return false|object */ - public function destroy_broadcast(int $id) + public function delete_broadcast(int $id) { return $this->delete(sprintf('broadcasts/%s', $id)); } diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 6454ecb..8d6dc78 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -51,6 +51,15 @@ class ConvertKitAPITest extends TestCase */ protected $subscriber_ids = []; + /** + * Broadcast IDs to delete on teardown of a test. + * + * @since 2.0.0 + * + * @var array + */ + protected $broadcast_ids = []; + /** * Load .env configuration into $_ENV superglobal, and initialize the API * class before each test. @@ -97,6 +106,11 @@ protected function tearDown(): void foreach ($this->subscriber_ids as $id) { $this->api->unsubscribe($id); } + + // Delete any Broadcasts. + foreach ($this->broadcast_ids as $id) { + $this->api->delete_broadcast($id); + } } /** @@ -3381,7 +3395,7 @@ public function testGetEmailTemplatesPagination() } /** - * Test that create_broadcast(), update_broadcast() and destroy_broadcast() works + * Test that create_broadcast(), update_broadcast() and delete_broadcast() works * when specifying valid published_at and send_at values. * * We do all tests in a single function, so we don't end up with unnecessary Broadcasts remaining @@ -3392,10 +3406,8 @@ public function testGetEmailTemplatesPagination() * * @return void */ - public function testCreateUpdateAndDestroyDraftBroadcast() + public function testCreateAndUpdateDraftBroadcast() { - $this->markTestIncomplete(); - // Create a broadcast first. $result = $this->api->create_broadcast( subject: 'Test Subject', @@ -3430,33 +3442,27 @@ public function testCreateUpdateAndDestroyDraftBroadcast() $this->assertEquals(null, $result['published_at']); $this->assertEquals(null, $result['send_at']); - // Destroy the broadcast. - $this->api->destroy_broadcast($broadcastID); + // Delete Broadcast. + $this->api->delete_broadcast($broadcastID); + $this->assertEquals(204, $this->api->getResponseInterface()->getStatusCode()); } /** - * Test that create_broadcast() and destroy_broadcast() works - * when specifying valid published_at and send_at values. - * - * We do both, so we don't end up with unnecessary Broadcasts remaining - * on the ConvertKit account when running tests, which might impact - * other tests that expect (or do not expect) specific Broadcasts. + * Test that create_broadcast() works when specifying valid published_at and send_at values. * * @since 1.0.0 * * @return void */ - public function testCreateAndDestroyPublicBroadcastWithValidDates() + public function testCreatePublicBroadcastWithValidDates() { - $this->markTestIncomplete(); - // Create DateTime object. $publishedAt = new DateTime('now'); $publishedAt->modify('+7 days'); $sendAt = new DateTime('now'); $sendAt->modify('+14 days'); - // Create a broadcast first. + // Create broadcast first. $result = $this->api->create_broadcast( subject: 'Test Subject', content: 'Test Content', @@ -3467,6 +3473,9 @@ public function testCreateAndDestroyPublicBroadcastWithValidDates() ); $broadcastID = $result->broadcast->id; + // Set broadcast_id to ensure broadcast is deleted after test. + $this->broadcast_ids[] = $broadcastID; + // Confirm the Broadcast saved. $result = get_object_vars($result->broadcast); $this->assertArrayHasKey('id', $result); @@ -3474,16 +3483,13 @@ public function testCreateAndDestroyPublicBroadcastWithValidDates() $this->assertEquals('Test Content', $result['content']); $this->assertEquals('Test Broadcast from PHP SDK', $result['description']); $this->assertEquals( - $publishedAt->format('Y-m-d') . 'T' . $publishedAt->format('H:i:s') . '.000Z', + $publishedAt->format('Y-m-d') . 'T' . $publishedAt->format('H:i:s') . 'Z', $result['published_at'] ); $this->assertEquals( - $sendAt->format('Y-m-d') . 'T' . $sendAt->format('H:i:s') . '.000Z', + $sendAt->format('Y-m-d') . 'T' . $sendAt->format('H:i:s') . 'Z', $result['send_at'] ); - - // Destroy the broadcast. - $this->api->destroy_broadcast($broadcastID); } /** @@ -3495,8 +3501,6 @@ public function testCreateAndDestroyPublicBroadcastWithValidDates() */ public function testGetBroadcast() { - $this->markTestIncomplete(); - $result = $this->api->get_broadcast($_ENV['CONVERTKIT_API_BROADCAST_ID']); $result = get_object_vars($result->broadcast); $this->assertEquals($result['id'], $_ENV['CONVERTKIT_API_BROADCAST_ID']); @@ -3512,8 +3516,6 @@ public function testGetBroadcast() */ public function testGetBroadcastWithInvalidBroadcastID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $this->api->get_broadcast(12345); } @@ -3527,8 +3529,6 @@ public function testGetBroadcastWithInvalidBroadcastID() */ public function testGetBroadcastStats() { - $this->markTestIncomplete(); - $result = $this->api->get_broadcast_stats($_ENV['CONVERTKIT_API_BROADCAST_ID']); $result = get_object_vars($result->broadcast); $this->assertArrayHasKey('id', $result); @@ -3550,8 +3550,6 @@ public function testGetBroadcastStats() */ public function testGetBroadcastStatsWithInvalidBroadcastID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $this->api->get_broadcast_stats(12345); } @@ -3566,26 +3564,22 @@ public function testGetBroadcastStatsWithInvalidBroadcastID() */ public function testUpdateBroadcastWithInvalidBroadcastID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $this->api->update_broadcast(12345); } /** - * Test that destroy_broadcast() throws a ClientException when an invalid + * Test that delete_broadcast() throws a ClientException when an invalid * broadcast ID is specified. * * @since 1.0.0 * * @return void */ - public function testDestroyBroadcastWithInvalidBroadcastID() + public function testDeleteBroadcastWithInvalidBroadcastID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); - $this->api->destroy_broadcast(12345); + $this->api->delete_broadcast(12345); } /** From 6152f0563219f95c66d7337c107358a2ac4332cd Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 28 Mar 2024 16:14:33 +0000 Subject: [PATCH 42/78] Added `testGetBroadcastsPagination` test --- src/ConvertKit_API.php | 78 ++++++++++++++++++------------------- tests/ConvertKitAPITest.php | 61 +++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 39 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index d6fb36b..31b1336 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1321,25 +1321,25 @@ public function get_broadcasts( /** * Creates a broadcast. * - * @param string $subject The broadcast email's subject. - * @param string $content The broadcast's email HTML content. - * @param string $description An internal description of this broadcast. - * @param boolean $public Specifies whether or not this is a public post. - * @param \DateTime $published_at Specifies the time that this post was published (applicable - * only to public posts). - * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create - * a draft broadcast. If set to a future time, this is the time that - * the broadcast will be scheduled to send. - * @param string $email_address Sending email address; leave blank to use your account's - * default sending email address. - * @param string $email_template_id ID of the email template to use; leave blank to use your - * account's default email template. - * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image - * (applicable only to public posts). - * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast - * post (applicable only to public posts). - * @param string $preview_text Specify the preview text of the email. - * @param array $subscriber_filter Filter subscriber(s) to send the email to. + * @param string $subject The broadcast email's subject. + * @param string $content The broadcast's email HTML content. + * @param string $description An internal description of this broadcast. + * @param boolean $public Specifies whether or not this is a public post. + * @param \DateTime $published_at Specifies the time that this post was published (applicable + * only to public posts). + * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create + * a draft broadcast. If set to a future time, this is the time that + * the broadcast will be scheduled to send. + * @param string $email_address Sending email address; leave blank to use your account's + * default sending email address. + * @param string $email_template_id ID of the email template to use; leave blank to use your + * account's default email template. + * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image + * (applicable only to public posts). + * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast + * post (applicable only to public posts). + * @param string $preview_text Specify the preview text of the email. + * @param array $subscriber_filter Filter subscriber(s) to send the email to. * * @see https://developers.convertkit.com/v4.html#create-a-broadcast * @@ -1427,26 +1427,26 @@ public function get_broadcast_stats(int $id) /** * Updates a broadcast. * - * @param integer $id Broadcast ID. - * @param string $subject The broadcast email's subject. - * @param string $content The broadcast's email HTML content. - * @param string $description An internal description of this broadcast. - * @param boolean $public Specifies whether or not this is a public post. - * @param \DateTime $published_at Specifies the time that this post was published (applicable - * only to public posts). - * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create - * a draft broadcast. If set to a future time, this is the time that - * the broadcast will be scheduled to send. - * @param string $email_address Sending email address; leave blank to use your account's - * default sending email address. - * @param string $email_template_id ID of the email template to use; leave blank to use your - * account's default email template. - * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image - * (applicable only to public posts). - * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast - * post (applicable only to public posts). - * @param string $preview_text Specify the preview text of the email. - * @param array $subscriber_filter Filter subscriber(s) to send the email to. + * @param integer $id Broadcast ID. + * @param string $subject The broadcast email's subject. + * @param string $content The broadcast's email HTML content. + * @param string $description An internal description of this broadcast. + * @param boolean $public Specifies whether or not this is a public post. + * @param \DateTime $published_at Specifies the time that this post was published (applicable + * only to public posts). + * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create + * a draft broadcast. If set to a future time, this is the time that + * the broadcast will be scheduled to send. + * @param string $email_address Sending email address; leave blank to use your account's + * default sending email address. + * @param string $email_template_id ID of the email template to use; leave blank to use your + * account's default email template. + * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image + * (applicable only to public posts). + * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast + * post (applicable only to public posts). + * @param string $preview_text Specify the preview text of the email. + * @param array $subscriber_filter Filter subscriber(s) to send the email to. * * @see https://developers.convertkit.com/#create-a-broadcast * diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 8d6dc78..674af1c 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -3394,6 +3394,67 @@ public function testGetEmailTemplatesPagination() $this->assertTrue($result->pagination->has_next_page); } + + /** + * Test that get_broadcasts() returns the expected data + * when pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetBroadcastsPagination() + { + $result = $this->api->get_broadcasts( + per_page: 1 + ); + + // Assert broadcasts and pagination exist. + $this->assertDataExists($result, 'broadcasts'); + $this->assertPaginationExists($result); + + // Assert a single broadcast was returned. + $this->assertCount(1, $result->broadcasts); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->get_broadcasts( + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert broadcasts and pagination exist. + $this->assertDataExists($result, 'broadcasts'); + $this->assertPaginationExists($result); + + // Assert a single broadcast was returned. + $this->assertCount(1, $result->broadcasts); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_broadcasts( + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert broadcasts and pagination exist. + $this->assertDataExists($result, 'broadcasts'); + $this->assertPaginationExists($result); + + // Assert a single broadcast was returned. + $this->assertCount(1, $result->broadcasts); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + } + /** * Test that create_broadcast(), update_broadcast() and delete_broadcast() works * when specifying valid published_at and send_at values. From 2434844b3abdbfffe18182240fb4d6fb53f9cb2d Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 28 Mar 2024 16:26:22 +0000 Subject: [PATCH 43/78] Added `include_total_count` to pagination args --- src/ConvertKit_API.php | 159 +++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 84 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index cc39343..5a540cc 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -496,7 +496,7 @@ public function get_form_subscriptions( } // Build pagination parameters. - $options = $this->build_pagination_params( + $options = $this->build_total_count_and_pagination_params( params: $options, after_cursor: $after_cursor, before_cursor: $before_cursor, @@ -525,7 +525,7 @@ public function get_sequences(string $after_cursor = '', string $before_cursor = { return $this->get( endpoint: 'sequences', - args: $this->build_pagination_params( + args: $this->build_total_count_and_pagination_params( after_cursor: $after_cursor, before_cursor: $before_cursor, per_page: $per_page @@ -616,7 +616,7 @@ public function get_sequence_subscriptions( } // Build pagination parameters. - $options = $this->build_pagination_params( + $options = $this->build_total_count_and_pagination_params( params: $options, after_cursor: $after_cursor, before_cursor: $before_cursor, @@ -631,21 +631,27 @@ public function get_sequence_subscriptions( } /** - * Gets tags + * List tags. * - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-tags * - * @return false|mixed + * @return false|array */ - public function get_tags(string $after_cursor = '', string $before_cursor = '', int $per_page = 100) - { + public function get_tags( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { return $this->get( endpoint: 'tags', - args: $this->build_pagination_params( + args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, after_cursor: $after_cursor, before_cursor: $before_cursor, per_page: $per_page @@ -825,7 +831,7 @@ public function get_tag_subscriptions( } // Build pagination parameters. - $options = $this->build_pagination_params( + $options = $this->build_total_count_and_pagination_params( params: $options, after_cursor: $after_cursor, before_cursor: $before_cursor, @@ -859,21 +865,15 @@ public function get_email_templates( string $before_cursor = '', int $per_page = 100 ) { - // Build parameters. - $options = ['include_total_count' => $include_total_count]; - - // Build pagination parameters. - $options = $this->build_pagination_params( - params: $options, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ); - // Send request. return $this->get( endpoint: 'email_templates', - args: $options + args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) ); } @@ -993,19 +993,20 @@ public function get_resources(string $resource) } /** - * Get subscribers. + * List subscribers. * - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param string $email_address Search susbcribers by email address. This is an exact match search. - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $updated_after Filter subscribers who have been updated after this date. - * @param \DateTime $updated_before Filter subscribers who have been updated before this date. - * @param string $sort_field Sort Field (id|updated_at|cancelled_at). - * @param string $sort_order Sort Order (asc|desc). - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param string $email_address Search susbcribers by email address. This is an exact match search. + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $updated_after Filter subscribers who have been updated after this date. + * @param \DateTime $updated_before Filter subscribers who have been updated before this date. + * @param string $sort_field Sort Field (id|updated_at|cancelled_at). + * @param string $sort_order Sort Order (asc|desc). + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @since 2.0.0 * @@ -1022,6 +1023,7 @@ public function get_subscribers( \DateTime $updated_before = null, string $sort_field = 'id', string $sort_order = 'desc', + bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', int $per_page = 100 @@ -1055,8 +1057,9 @@ public function get_subscribers( } // Build pagination parameters. - $options = $this->build_pagination_params( + $options = $this->build_total_count_and_pagination_params( params: $options, + include_total_count: $include_total_count, after_cursor: $after_cursor, before_cursor: $before_cursor, per_page: $per_page @@ -1257,10 +1260,11 @@ public function unsubscribe(int $subscriber_id) /** * Get a list of the tags for a subscriber. * - * @param integer $subscriber_id Subscriber ID. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param integer $subscriber_id Subscriber ID. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-tags-for-a-subscriber * @@ -1268,13 +1272,15 @@ public function unsubscribe(int $subscriber_id) */ public function get_subscriber_tags( int $subscriber_id, + bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', int $per_page = 100 ) { return $this->get( endpoint: sprintf('subscribers/%s/tags', $subscriber_id), - args: $this->build_pagination_params( + args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, after_cursor: $after_cursor, before_cursor: $before_cursor, per_page: $per_page @@ -1588,21 +1594,15 @@ public function get_custom_fields( string $before_cursor = '', int $per_page = 100 ) { - // Build parameters. - $options = ['include_total_count' => $include_total_count]; - - // Build pagination parameters. - $options = $this->build_pagination_params( - params: $options, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ); - // Send request. return $this->get( endpoint: 'custom_fields', - args: $options + args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) ); } @@ -1716,21 +1716,15 @@ public function get_purchases( string $before_cursor = '', int $per_page = 100 ) { - // Build parameters. - $options = ['include_total_count' => $include_total_count]; - - // Build pagination parameters. - $options = $this->build_pagination_params( - params: $options, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ); - // Send request. return $this->get( endpoint: 'purchases', - args: $options + args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) ); } @@ -1818,21 +1812,15 @@ public function get_segments( string $before_cursor = '', int $per_page = 100 ) { - // Build parameters. - $options = ['include_total_count' => $include_total_count]; - - // Build pagination parameters. - $options = $this->build_pagination_params( - params: $options, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ); - // Send request. return $this->get( endpoint: 'segments', - args: $options + args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) ); } @@ -1975,23 +1963,26 @@ private function strip_html_head_body_tags(string $markup) } /** - * Adds pagination parameters to the given array of existing API parameters. + * Adds total count and pagination parameters to the given array of existing API parameters. * - * @param array $params API parameters. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param array $params API parameters. + * @param boolean $include_total_count Return total count of records. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @since 2.0.0 * * @return array */ - private function build_pagination_params( + private function build_total_count_and_pagination_params( array $params = [], + bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', int $per_page = 100 ) { + $params['include_total_count'] = $include_total_count; if (!empty($after_cursor)) { $params['after'] = $after_cursor; } From 0df40a7ef5ccb63d074506128eeab9db06e567fd Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 28 Mar 2024 16:35:17 +0000 Subject: [PATCH 44/78] Added `include_total_count` to methods --- src/ConvertKit_API.php | 134 +++++++++++++++++++++-------------------- 1 file changed, 68 insertions(+), 66 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 5a540cc..8cf7015 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -451,15 +451,16 @@ public function add_subscriber_to_form(int $form_id, int $subscriber_id) /** * List subscribers for a form * - * @param integer $form_id Form ID. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. - * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param integer $form_id Form ID. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. + * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-form * @@ -472,6 +473,7 @@ public function get_form_subscriptions( \DateTime $created_before = null, \DateTime $added_after = null, \DateTime $added_before = null, + bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', int $per_page = 100 @@ -495,24 +497,23 @@ public function get_form_subscriptions( $options['added_before'] = $added_before->format('Y-m-d'); } - // Build pagination parameters. - $options = $this->build_total_count_and_pagination_params( - params: $options, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ); - // Send request. return $this->get( endpoint: sprintf('forms/%s/subscribers', $form_id), - args: $options + args: $this->build_total_count_and_pagination_params( + params: $options, + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) ); } /** * Gets sequences * + * @param boolean $include_total_count To include the total count of records in the response, use true. * @param string $after_cursor Return results after the given pagination cursor. * @param string $before_cursor Return results before the given pagination cursor. * @param integer $per_page Number of results to return. @@ -521,11 +522,16 @@ public function get_form_subscriptions( * * @return false|mixed */ - public function get_sequences(string $after_cursor = '', string $before_cursor = '', int $per_page = 100) - { + public function get_sequences( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { return $this->get( endpoint: 'sequences', args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, after_cursor: $after_cursor, before_cursor: $before_cursor, per_page: $per_page @@ -571,15 +577,16 @@ public function add_subscriber_to_sequence(int $sequence_id, int $subscriber_id) /** * List subscribers for a sequence * - * @param integer $sequence_id Sequence ID. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. - * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param integer $sequence_id Sequence ID. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. + * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-sequence * @@ -592,6 +599,7 @@ public function get_sequence_subscriptions( \DateTime $created_before = null, \DateTime $added_after = null, \DateTime $added_before = null, + bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', int $per_page = 100 @@ -615,18 +623,15 @@ public function get_sequence_subscriptions( $options['added_before'] = $added_before->format('Y-m-d'); } - // Build pagination parameters. - $options = $this->build_total_count_and_pagination_params( - params: $options, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ); - // Send request. return $this->get( endpoint: sprintf('sequences/%s/subscribers', $sequence_id), - args: $options + args: $this->build_total_count_and_pagination_params( + params: $options, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) ); } @@ -786,15 +791,16 @@ public function remove_tag_from_subscriber_by_email(int $tag_id, string $email_a /** * List subscribers for a tag * - * @param integer $tag_id Tag ID. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $tagged_after Filter subscribers who have been tagged after this date. - * @param \DateTime $tagged_before Filter subscribers who have been tagged before this date. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param integer $tag_id Tag ID. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $tagged_after Filter subscribers who have been tagged after this date. + * @param \DateTime $tagged_before Filter subscribers who have been tagged before this date. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-tag * @@ -807,6 +813,7 @@ public function get_tag_subscriptions( \DateTime $created_before = null, \DateTime $tagged_after = null, \DateTime $tagged_before = null, + bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', int $per_page = 100 @@ -830,18 +837,16 @@ public function get_tag_subscriptions( $options['tagged_before'] = $tagged_before->format('Y-m-d'); } - // Build pagination parameters. - $options = $this->build_total_count_and_pagination_params( - params: $options, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ); - // Send request. return $this->get( endpoint: sprintf('tags/%s/subscribers', $tag_id), - args: $options + args: $this->build_total_count_and_pagination_params( + params: $options, + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) ); } @@ -1056,19 +1061,16 @@ public function get_subscribers( $options['sort_order'] = $sort_order; } - // Build pagination parameters. - $options = $this->build_total_count_and_pagination_params( - params: $options, - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ); - // Send request. return $this->get( endpoint: 'subscribers', - args: $options + args: $this->build_total_count_and_pagination_params( + params: $options, + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) ); } From 7b5fcef04da621e066c23ac853dd12e134deaeb1 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 28 Mar 2024 16:37:42 +0000 Subject: [PATCH 45/78] Started tests --- tests/ConvertKitAPITest.php | 165 ++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 6454ecb..0e44ce5 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -755,6 +755,30 @@ public function testGetFormSubscriptions() $this->assertPaginationExists($result); } + /** + * Test that get_form_subscriptions() returns the expected data + * when the total count is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetFormSubscriptionsWithTotalCount() + { + $result = $this->api->get_form_subscriptions( + form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'], + include_total_count: true + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + /** * Test that get_form_subscriptions() returns the expected data * when a valid Form ID is specified and the subscription status @@ -1022,6 +1046,29 @@ public function testGetSequences() $this->assertArrayHasKey('created_at', $sequence); } + /** + * Test that get_sequences() returns the expected data + * when the total count is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSequencesWithTotalCount() + { + $result = $this->api->get_sequences( + include_total_count: true + ); + + // Assert sequences and pagination exist. + $this->assertDataExists($result, 'sequences'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + /** * Test that get_sequences() returns the expected data when * pagination parameters and per_page limits are specified. @@ -1205,6 +1252,30 @@ public function testGetSequenceSubscriptions() $this->assertPaginationExists($result); } + /** + * Test that get_sequence_subscriptions() returns the expected data + * when the total count is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSequencesWithTotalCount() + { + $result = $this->api->get_sequence_subscriptions( + sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], + include_total_count: true + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + /** * Test that get_sequence_subscriptions() returns the expected data * when a valid Sequence ID is specified and the subscription status @@ -1470,6 +1541,29 @@ public function testGetTags() $this->assertArrayHasKey('created_at', $tag); } + /** + * Test that get_tags() returns the expected data + * when the total count is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetTagsWithTotalCount() + { + $result = $this->api->get_tags( + include_total_count: true + ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + /** * Test that get_tags() returns the expected data * when pagination parameters and per_page limits are specified. @@ -1948,6 +2042,30 @@ public function testGetTagSubscriptions() $this->assertPaginationExists($result); } + /** + * Test that get_tag_subscriptions() returns the expected data + * when the total count is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetTagSubscriptionsWithTotalCount() + { + $result = $this->api->get_tag_subscriptions( + tag_id: (int) $_ENV['CONVERTKIT_API_TAG_ID'], + include_total_count: true + ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + /** * Test that get_tag_subscriptions() returns the expected data * when a valid Tag ID is specified and the subscription status @@ -2346,6 +2464,29 @@ public function testGetSubscribers() $this->assertPaginationExists($result); } + /** + * Test that get_subscribers() returns the expected data + * when the total count is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSubscribersWithTotalCount() + { + $result = $this->api->get_subscribers( + include_total_count: true + ); + + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + /** * Test that get_subscribers() returns the expected data when * searching by an email address. @@ -3207,6 +3348,30 @@ public function testGetSubscriberTags() $this->assertPaginationExists($result); } + /** + * Test that get_subscriber_tags() returns the expected data + * when the total count is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSubscriberTagsWithTotalCount() + { + $result = $this->api->get_subscriber_tags( + subscriber_id: (int) $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], + include_total_count: true + ); + + // Assert tags and pagination exist. + $this->assertDataExists($result, 'tags'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + /** * Test that get_subscriber_tags() throws a ClientException when an invalid * subscriber ID is specified. From 2a3357817158c62838045be679f47063d00a3c11 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 28 Mar 2024 16:38:04 +0000 Subject: [PATCH 46/78] Coding standards --- src/ConvertKit_API.php | 120 ++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 8cf7015..4b2e3af 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -451,16 +451,16 @@ public function add_subscriber_to_form(int $form_id, int $subscriber_id) /** * List subscribers for a form * - * @param integer $form_id Form ID. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. - * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param integer $form_id Form ID. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. + * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-form * @@ -513,10 +513,10 @@ public function get_form_subscriptions( /** * Gets sequences * - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-sequences * @@ -577,16 +577,16 @@ public function add_subscriber_to_sequence(int $sequence_id, int $subscriber_id) /** * List subscribers for a sequence * - * @param integer $sequence_id Sequence ID. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. - * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param integer $sequence_id Sequence ID. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. + * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-sequence * @@ -638,10 +638,10 @@ public function get_sequence_subscriptions( /** * List tags. * - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-tags * @@ -791,16 +791,16 @@ public function remove_tag_from_subscriber_by_email(int $tag_id, string $email_a /** * List subscribers for a tag * - * @param integer $tag_id Tag ID. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $tagged_after Filter subscribers who have been tagged after this date. - * @param \DateTime $tagged_before Filter subscribers who have been tagged before this date. - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param integer $tag_id Tag ID. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $tagged_after Filter subscribers who have been tagged after this date. + * @param \DateTime $tagged_before Filter subscribers who have been tagged before this date. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-tag * @@ -1000,18 +1000,18 @@ public function get_resources(string $resource) /** * List subscribers. * - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param string $email_address Search susbcribers by email address. This is an exact match search. - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $updated_after Filter subscribers who have been updated after this date. - * @param \DateTime $updated_before Filter subscribers who have been updated before this date. - * @param string $sort_field Sort Field (id|updated_at|cancelled_at). - * @param string $sort_order Sort Order (asc|desc). - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param string $email_address Search susbcribers by email address. This is an exact match search. + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $updated_after Filter subscribers who have been updated after this date. + * @param \DateTime $updated_before Filter subscribers who have been updated before this date. + * @param string $sort_field Sort Field (id|updated_at|cancelled_at). + * @param string $sort_order Sort Order (asc|desc). + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @since 2.0.0 * @@ -1262,11 +1262,11 @@ public function unsubscribe(int $subscriber_id) /** * Get a list of the tags for a subscriber. * - * @param integer $subscriber_id Subscriber ID. - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param integer $subscriber_id Subscriber ID. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @see https://developers.convertkit.com/v4.html#list-tags-for-a-subscriber * @@ -1967,11 +1967,11 @@ private function strip_html_head_body_tags(string $markup) /** * Adds total count and pagination parameters to the given array of existing API parameters. * - * @param array $params API parameters. - * @param boolean $include_total_count Return total count of records. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param array $params API parameters. + * @param boolean $include_total_count Return total count of records. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. * * @since 2.0.0 * From 54bf2a84a38e1d627032114e7246f7311cf82cc1 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 28 Mar 2024 17:00:14 +0000 Subject: [PATCH 47/78] Fix failing tests --- src/ConvertKit_API.php | 1 + tests/ConvertKitAPITest.php | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 4b2e3af..07ca60d 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -628,6 +628,7 @@ public function get_sequence_subscriptions( endpoint: sprintf('sequences/%s/subscribers', $sequence_id), args: $this->build_total_count_and_pagination_params( params: $options, + include_total_count: $include_total_count, after_cursor: $after_cursor, before_cursor: $before_cursor, per_page: $per_page diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 0e44ce5..dc146c0 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1260,7 +1260,7 @@ public function testGetSequenceSubscriptions() * * @return void */ - public function testGetSequencesWithTotalCount() + public function testGetSequenceSubscriptionsWithTotalCount() { $result = $this->api->get_sequence_subscriptions( sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], @@ -2057,8 +2057,8 @@ public function testGetTagSubscriptionsWithTotalCount() include_total_count: true ); - // Assert tags and pagination exist. - $this->assertDataExists($result, 'tags'); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); $this->assertPaginationExists($result); // Assert total count is included. From 1a35c36dcc0e4b52fea222ce3e5a9f9126af018e Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 29 Mar 2024 14:56:51 +0000 Subject: [PATCH 48/78] Add `sku` to products; reflect required and optional fields --- src/ConvertKit_API.php | 44 +++++++++++++------ tests/ConvertKitAPITest.php | 84 ++++++++++++++++++++++++++++++++----- 2 files changed, 104 insertions(+), 24 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 07ca60d..e0e0d53 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1750,15 +1750,16 @@ public function get_purchase(int $purchase_id) * * @param string $email_address Email Address. * @param string $transaction_id Transaction ID. + * @param array $products Products. + * @param string $currency ISO Currency Code. + * @param string $first_name First Name. * @param string $status Order Status. * @param float $subtotal Subtotal. * @param float $tax Tax. * @param float $shipping Shipping. * @param float $discount Discount. * @param float $total Total. - * @param string $currency ISO Currency Code. * @param \DateTime $transaction_time Transaction date and time. - * @param array $products Products. * * @see https://developers.convertkit.com/v4.html#create-a-purchase * @@ -1767,31 +1768,48 @@ public function get_purchase(int $purchase_id) public function create_purchase( string $email_address, string $transaction_id, - string $status, + array $products, + string $currency = 'USD', + string $first_name = null, + string $status = null, float $subtotal = 0, float $tax = 0, float $shipping = 0, float $discount = 0, float $total = 0, - string $currency = 'usd', - \DateTime $transaction_time = null, - array $products = [] + \DateTime $transaction_time = null ) { // Build parameters. $options = [ + // Required fields. 'email_address' => $email_address, 'transaction_id' => $transaction_id, + 'products' => $products, + 'currency' => $currency, // Required, but if not provided, API will default to USD. + + // Optional fields. + 'first_name' => $first_name, 'status' => $status, 'subtotal' => $subtotal, 'tax' => $tax, 'shipping' => $shipping, 'discount' => $discount, 'total' => $total, - 'currency' => $currency, 'transaction_time' => (!is_null($transaction_time) ? $transaction_time->format('Y-m-d H:i:s') : ''), - 'products' => $products, ]; + // Iterate through options, removing blank and null entries. + foreach ($options as $key => $value) { + if (is_null($value)) { + unset($options[$key]); + continue; + } + + if (is_string($value) && strlen($value) === 0) { + unset($options[$key]); + } + } + return $this->post('purchases', $options); } @@ -2015,8 +2033,8 @@ public function get(string $endpoint, array $args = []) /** * Performs a POST request to the API. * - * @param string $endpoint API Endpoint. - * @param array>> $args Request arguments. + * @param string $endpoint API Endpoint. + * @param array>> $args Request arguments. * * @return false|mixed */ @@ -2054,9 +2072,9 @@ public function delete(string $endpoint, array $args = []) /** * Performs an API request using Guzzle. * - * @param string $endpoint API Endpoint. - * @param string $method Request method. - * @param array>> $args Request arguments. + * @param string $endpoint API Endpoint. + * @param string $method Request method. + * @param array>> $args Request arguments. * * @throws \Exception If JSON encoding arguments failed. * diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index dc146c0..76d6ca2 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -4226,19 +4226,14 @@ public function testGetPurchaseWithInvalidID() public function testCreatePurchase() { $purchase = $this->api->create_purchase( + // Required fields. email_address: $this->generateEmailAddress(), transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'), - status: 'paid', - subtotal: 20.00, - tax: 2.00, - shipping: 2.00, - discount: 3.00, - total: 21.00, currency: 'usd', - transaction_time: new DateTime('now'), products: [ [ 'name' => 'Floppy Disk (512k)', + 'sku' => '7890-ijkl', 'pid' => 9999, 'lid' => 7777, 'quantity' => 2, @@ -4246,12 +4241,22 @@ public function testCreatePurchase() ], [ 'name' => 'Telephone Cord (data)', + 'sku' => 'mnop-1234', 'pid' => 5555, 'lid' => 7778, 'quantity' => 1, 'unit_price' => 10.00, ], ], + // Optional fields. + first_name: 'Tim', + status: 'paid', + subtotal: 20.00, + tax: 2.00, + shipping: 2.00, + discount: 3.00, + total: 21.00, + transaction_time: new DateTime('now'), ); $this->assertInstanceOf('stdClass', $purchase); @@ -4260,19 +4265,76 @@ public function testCreatePurchase() /** * Test that create_purchase() throws a ClientException when an invalid - * purchase data is specified. + * email address is specified. * - * @since 1.0.0 + * @since 2.0.0 * * @return void */ - public function testCreatePurchaseWithInvalidData() + public function testCreatePurchaseWithInvalidEmailAddress() { $this->expectException(ClientException::class); $this->api->create_purchase( email_address: 'not-an-email-address', transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'), - status: 'paid' + currency: 'usd', + products: [ + [ + 'name' => 'Floppy Disk (512k)', + 'sku' => '7890-ijkl', + 'pid' => 9999, + 'lid' => 7777, + 'quantity' => 2, + 'unit_price' => 5.00, + ], + ], + ); + } + + /** + * Test that create_purchase() throws a ClientException when a blank + * transaction ID is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreatePurchaseWithBlankTransactionID() + { + $this->expectException(ClientException::class); + $this->api->create_purchase( + email_address: $this->generateEmailAddress(), + transaction_id: '', + currency: 'usd', + products: [ + [ + 'name' => 'Floppy Disk (512k)', + 'sku' => '7890-ijkl', + 'pid' => 9999, + 'lid' => 7777, + 'quantity' => 2, + 'unit_price' => 5.00, + ], + ], + ); + } + + /** + * Test that create_purchase() throws a ClientException when no products + * are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testCreatePurchaseWithNoProducts() + { + $this->expectException(ClientException::class); + $this->api->create_purchase( + email_address: $this->generateEmailAddress(), + transaction_id: str_shuffle('wfervdrtgsdewrafvwefds'), + currency: 'usd', + products: [], ); } From 1ffa58ec460e7e02f2df0e7a6513c7e9909023ea Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 29 Mar 2024 15:47:32 +0000 Subject: [PATCH 49/78] Mock `create_tags` and `create_tags` tests --- tests/ConvertKitAPITest.php | 39 ++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index dc146c0..872caf7 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1627,9 +1627,24 @@ public function testGetTagsPagination() public function testCreateTag() { $tagName = 'Tag Test ' . mt_rand(); + + // Add mock handler for this API request, as the API doesn't provide + // a method to delete tags to cleanup the test. + $this->api = $this->mockResponse( + api: $this->api, + responseBody: [ + 'tag' => [ + 'id' => 12345, + 'name' => $tagName, + 'created_at' => date('Y-m-d') . 'T' . date('H:i:s') . 'Z', + ], + ] + ); + + // Send request. $result = $this->api->create_tag($tagName); - // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. + // Assert response contains correct data. $tag = get_object_vars($result->tag); $this->assertArrayHasKey('id', $tag); $this->assertArrayHasKey('name', $tag); @@ -1678,6 +1693,28 @@ public function testCreateTags() 'Tag Test ' . mt_rand(), 'Tag Test ' . mt_rand(), ]; + + // Add mock handler for this API request, as the API doesn't provide + // a method to delete tags to cleanup the test. + $this->api = $this->mockResponse( + api: $this->api, + responseBody: [ + 'tags' => [ + [ + 'id' => 12345, + 'name' => $tagNames[0], + 'created_at' => date('Y-m-d') . 'T' . date('H:i:s') . 'Z', + ], + [ + 'id' => 23456, + 'name' => $tagNames[1], + 'created_at' => date('Y-m-d') . 'T' . date('H:i:s') . 'Z', + ], + ], + 'failures' => [], + ] + ); + $result = $this->api->create_tags($tagNames); // Assert no failures. From bfac5234cf9f3e639b38b38058fb269979002ba3 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 29 Mar 2024 16:16:58 +0000 Subject: [PATCH 50/78] Update `get_forms` and `get_landing_pages` methods --- src/ConvertKit_API.php | 210 ++++++++++++++++------------------------- 1 file changed, 83 insertions(+), 127 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 07ca60d..914ee24 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -386,31 +386,102 @@ public function get_growth_stats(\DateTime $starting = null, \DateTime $ending = } /** - * Gets all forms. - * + * Get forms. + * * @since 1.0.0 * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * * @see https://developers.convertkit.com/v4.html#convertkit-api-forms * - * @return false|mixed + * @return false|array */ - public function get_forms() - { - return $this->get_resources('forms'); + public function get_forms( + bool $include_archived = false, + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Get forms and landing pages. + $result = $this->get( + endpoint: 'forms', + args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) + ); + + // Exclude archived and landing pages. + foreach ($result->forms as $index => $form) { + // Exclude hosted forms i.e. landing pages. + if ($form->type === 'hosted') { + unset($result->forms[ $index ] ); + continue; + } + + // Exclude archived forms, if required. + if (!$include_archived && isset($form->archived) && $form->archived) { + unset($result->forms[ $index ] ); + continue; + } + } + + return $result; } /** - * Gets all landing pages. - * + * Get landing pages. + * * @since 1.0.0 * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * * @see https://developers.convertkit.com/v4.html#convertkit-api-forms * - * @return false|mixed + * @return false|array */ - public function get_landing_pages() - { - return $this->get_resources('landing_pages'); + public function get_landing_pages( + bool $include_archived = false, + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + $result = $this->get( + endpoint: 'forms', + args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) + ); + + // Exclude archived and non-hosted forms. + foreach ($result->forms as $index => $form) { + // Exclude non-hosted forms i.e. forms. + if ($form->type !== 'hosted') { + unset($result->forms[ $index ] ); + continue; + } + + // Exclude archived forms, if required. + if (!$include_archived && isset($form->archived) && $form->archived) { + unset($result->forms[ $index ] ); + continue; + } + } + + return $result; } /** @@ -883,121 +954,6 @@ public function get_email_templates( ); } - /** - * Gets a resource index - * Possible resources: forms, landing_pages, subscription_forms, tags - * - * GET /{$resource}/ - * - * @param string $resource Resource type. - * - * @throws \InvalidArgumentException If the resource argument is not a supported resource type. - * - * @return array API response - */ - public function get_resources(string $resource) - { - // Assign the resource to the request variable. - $request = $resource; - - // Landing pages are included in the /forms endpoint. - if ($resource === 'landing_pages') { - $request = 'forms'; - } - - // Fetch resources. - $resources = $this->get($request); - - $this->create_log(sprintf('%s response %s', $resource, json_encode($resources))); - - // Return a blank array if no resources exist. - if (!$resources) { - $this->create_log('No resources'); - return []; - } - - // Build array of resources. - $_resource = []; - switch ($resource) { - // Forms. - case 'forms': - // Bail if no forms are set. - if (!isset($resources->forms)) { - $this->create_log('No form resources'); - return []; - } - - // Build array of forms. - foreach ($resources->forms as $form) { - // Exclude archived forms. - if (isset($form->archived) && $form->archived) { - continue; - } - - // Exclude hosted forms. - if ($form->type === 'hosted') { - continue; - } - - $_resource[] = $form; - } - break; - - // Landing Pages. - case 'landing_pages': - // Bail if no landing pages are set. - if (!isset($resources->forms)) { - $this->create_log('No landing page resources'); - return []; - } - - foreach ($resources->forms as $form) { - // Exclude archived landing pages. - if (isset($form->archived) && $form->archived) { - continue; - } - - // Exclude non-hosted (i.e. forms). - if ($form->type !== 'hosted') { - continue; - } - - $_resource[] = $form; - } - break; - - // Subscription Forms. - case 'subscription_forms': - // Exclude archived subscription forms. - foreach ($resources as $mapping) { - if (isset($mapping->archived) && $mapping->archived) { - continue; - } - - $_resource[$mapping->id] = $mapping->form_id; - } - break; - - // Tags. - case 'tags': - // Bail if no tags are set. - if (!isset($resources->tags)) { - $this->create_log('No tag resources'); - return []; - } - - foreach ($resources->tags as $tag) { - $_resource[] = $tag; - } - break; - - default: - throw new \InvalidArgumentException('An unsupported resource was specified.'); - }//end switch - - return $_resource; - } - /** * List subscribers. * From 6e5a0b01c4d015fc5e1ace047aa372921cc45c79 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 29 Mar 2024 16:17:01 +0000 Subject: [PATCH 51/78] Started tests --- tests/ConvertKitAPITest.php | 69 ++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 23 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index dc146c0..c30fccb 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -697,20 +697,33 @@ public function testGetGrowthStatsWithEndDate() public function testGetForms() { $result = $this->api->get_forms(); - $this->assertIsArray($result); - // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. - $form = get_object_vars($result[0]); - $this->assertArrayHasKey('id', $form); - $this->assertArrayHasKey('name', $form); - $this->assertArrayHasKey('created_at', $form); - $this->assertArrayHasKey('type', $form); - $this->assertArrayHasKey('format', $form); - $this->assertArrayHasKey('embed_js', $form); - $this->assertArrayHasKey('embed_url', $form); - $this->assertArrayHasKey('archived', $form); + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Iterate through each form, confirming no landing pages were included. + foreach ($result->forms as $form) { + $form = get_object_vars($form); + + // Assert shape of object is valid. + $this->assertArrayHasKey('id', $form); + $this->assertArrayHasKey('name', $form); + $this->assertArrayHasKey('created_at', $form); + $this->assertArrayHasKey('type', $form); + $this->assertArrayHasKey('format', $form); + $this->assertArrayHasKey('embed_js', $form); + $this->assertArrayHasKey('embed_url', $form); + $this->assertArrayHasKey('archived', $form); + + // Assert form isn't archived or a landing page. + $this->assertFalse($form['archived']); + $this->assertEquals($form['type'], 'embed'); + } } + + /** * Test that get_landing_pages() returns the expected data. * @@ -721,19 +734,29 @@ public function testGetForms() public function testGetLandingPages() { $result = $this->api->get_landing_pages(); - $this->assertIsArray($result); - // Convert to array to check for keys, as assertObjectHasAttribute() will be deprecated in PHPUnit 10. - $landingPage = get_object_vars($result[0]); - $this->assertArrayHasKey('id', $landingPage); - $this->assertArrayHasKey('name', $landingPage); - $this->assertArrayHasKey('created_at', $landingPage); - $this->assertArrayHasKey('type', $landingPage); - $this->assertEquals('hosted', $landingPage['type']); - $this->assertArrayHasKey('format', $landingPage); - $this->assertArrayHasKey('embed_js', $landingPage); - $this->assertArrayHasKey('embed_url', $landingPage); - $this->assertArrayHasKey('archived', $landingPage); + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Iterate through each landing page, confirming no forms were included. + foreach ($result->forms as $form) { + $form = get_object_vars($form); + + // Assert shape of object is valid. + $this->assertArrayHasKey('id', $form); + $this->assertArrayHasKey('name', $form); + $this->assertArrayHasKey('created_at', $form); + $this->assertArrayHasKey('type', $form); + $this->assertArrayHasKey('format', $form); + $this->assertArrayHasKey('embed_js', $form); + $this->assertArrayHasKey('embed_url', $form); + $this->assertArrayHasKey('archived', $form); + + // Assert form isn't archived and is hosted i.e. landing page. + $this->assertFalse($form['archived']); + $this->assertEquals($form['type'], 'hosted'); + } } /** From a6753fa0b6944d2ae1f997518e882ecb3c5abcab Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 1 Apr 2024 16:51:37 +0100 Subject: [PATCH 52/78] Coding standards --- src/ConvertKit_API.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 85802d8..f91831f 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -387,14 +387,15 @@ public function get_growth_stats(\DateTime $starting = null, \DateTime $ending = /** * Get forms. - * - * @since 1.0.0 * + * @param boolean $include_archived To include archived forms in the response, use true. * @param boolean $include_total_count To include the total count of records in the response, use true. * @param string $after_cursor Return results after the given pagination cursor. * @param string $before_cursor Return results before the given pagination cursor. * @param integer $per_page Number of results to return. * + * @since 1.0.0 + * * @see https://developers.convertkit.com/v4.html#convertkit-api-forms * * @return false|array @@ -421,13 +422,13 @@ public function get_forms( foreach ($result->forms as $index => $form) { // Exclude hosted forms i.e. landing pages. if ($form->type === 'hosted') { - unset($result->forms[ $index ] ); + unset($result->forms[$index]); continue; } // Exclude archived forms, if required. if (!$include_archived && isset($form->archived) && $form->archived) { - unset($result->forms[ $index ] ); + unset($result->forms[$index]); continue; } } @@ -437,14 +438,15 @@ public function get_forms( /** * Get landing pages. - * - * @since 1.0.0 * + * @param boolean $include_archived To include archived landing pages in the response, use true. * @param boolean $include_total_count To include the total count of records in the response, use true. * @param string $after_cursor Return results after the given pagination cursor. * @param string $before_cursor Return results before the given pagination cursor. * @param integer $per_page Number of results to return. * + * @since 1.0.0 + * * @see https://developers.convertkit.com/v4.html#convertkit-api-forms * * @return false|array @@ -470,13 +472,13 @@ public function get_landing_pages( foreach ($result->forms as $index => $form) { // Exclude non-hosted forms i.e. forms. if ($form->type !== 'hosted') { - unset($result->forms[ $index ] ); + unset($result->forms[$index]); continue; } // Exclude archived forms, if required. if (!$include_archived && isset($form->archived) && $form->archived) { - unset($result->forms[ $index ] ); + unset($result->forms[$index]); continue; } } From 95a2256aec287030948d01b1d1e15fb30877ffe0 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 1 Apr 2024 16:51:45 +0100 Subject: [PATCH 53/78] Add form pagination and total count tests --- tests/ConvertKitAPITest.php | 83 ++++++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 1 deletion(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index adc89be..6b51eba 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -722,7 +722,88 @@ public function testGetForms() } } - + /** + * Test that get_forms() returns the expected data + * when the total count is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetFormsWithTotalCount() + { + $result = $this->api->get_forms( + include_total_count: true + ); + + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + + /** + * Test that get_forms() returns the expected data when pagination parameters + * and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetFormsPagination() + { + $result = $this->api->get_forms( + per_page: 2 + ); + + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Assert a single form was returned. + $this->assertCount(1, $result->forms); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->get_forms( + per_page: 2, + after_cursor: $result->pagination->end_cursor + ); + + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Assert a single form was returned. + $this->assertCount(1, $result->forms); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_forms( + per_page: 2, + before_cursor: $result->pagination->start_cursor + ); + + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Assert a single form was returned. + $this->assertCount(1, $result->forms); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + } /** * Test that get_landing_pages() returns the expected data. From c63a6ae52590d480b59766579a6265f67d563165 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 1 Apr 2024 16:56:12 +0100 Subject: [PATCH 54/78] Fix undefined method `build_pagination_params --- src/ConvertKit_API.php | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index fb4df8b..a3460de 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1309,21 +1309,15 @@ public function get_broadcasts( string $before_cursor = '', int $per_page = 100 ) { - // Build parameters. - $options = ['include_total_count' => $include_total_count]; - - // Build pagination parameters. - $options = $this->build_pagination_params( - params: $options, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ); - // Send request. return $this->get( endpoint: 'broadcasts', - args: $options + args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) ); } From 512ea0ae9c340ac0791bb4d01e6b5b850cb7d9d8 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 1 Apr 2024 17:05:30 +0100 Subject: [PATCH 55/78] Added landing page tests --- tests/ConvertKitAPITest.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 6b51eba..440cff9 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -840,6 +840,29 @@ public function testGetLandingPages() } } + /** + * Test that get_landing_pages() returns the expected data + * when the total count is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetLandingPagesWithTotalCount() + { + $result = $this->api->get_landing_pages( + include_total_count: true + ); + + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + /** * Test that get_form_subscriptions() returns the expected data * when a valid Form ID is specified. From 619765298531bc341f1708e3881849bf938cc1cc Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 1 Apr 2024 17:09:12 +0100 Subject: [PATCH 56/78] Remove deprecated `get_resources` tests --- tests/ConvertKitAPITest.php | 68 ------------------------------------- 1 file changed, 68 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 440cff9..7367bda 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -2438,74 +2438,6 @@ public function testGetTagSubscriptionsWithInvalidTagID() $result = $this->api->get_tag_subscriptions(12345); } - /** - * Test that get_resources() for Forms returns the expected data. - * - * @since 1.0.0 - * - * @return void - */ - public function testGetResourcesForms() - { - $result = $this->api->get_resources('forms'); - $this->assertIsArray($result); - } - - /** - * Test that get_resources() for Landing Pages returns the expected data. - * - * @since 1.0.0 - * - * @return void - */ - public function testGetResourcesLandingPages() - { - $result = $this->api->get_resources('landing_pages'); - $this->assertIsArray($result); - } - - /** - * Test that get_resources() for Subscription Forms returns the expected data. - * - * @since 1.0.0 - * - * @return void - */ - public function testGetResourcesSubscriptionForms() - { - $this->markTestIncomplete(); - $result = $this->api->get_resources('subscription_forms'); - $this->assertIsArray($result); - } - - /** - * Test that get_resources() for Tags returns the expected data. - * - * @since 1.0.0 - * - * @return void - */ - public function testGetResourcesTags() - { - $result = $this->api->get_resources('tags'); - $this->assertIsArray($result); - } - - /** - * Test that get_resources() throws a ClientException when an invalid - * resource type is specified. - * - * @since 1.0.0 - * - * @return void - */ - public function testGetResourcesInvalidResourceType() - { - $this->expectException(ClientException::class); - $result = $this->api->get_resources('invalid-resource-type'); - $this->assertIsArray($result); - } - /** * Test that add_subscriber_to_form_by_email() returns the expected data. * From 12c0618093421c03d98c8112c8d2e8d1f9e1bd52 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 1 Apr 2024 17:26:02 +0100 Subject: [PATCH 57/78] Add API support --- src/ConvertKit_API.php | 46 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index e0e0d53..f20b254 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -1485,6 +1485,38 @@ public function destroy_broadcast(int $id) return $this->delete(sprintf('broadcasts/%s', $id)); } + /** + * List webhooks. + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 2.0.0 + * + * @see https://developers.convertkit.com/v4.html#list-webhooks + * + * @return false|mixed + */ + public function get_webhooks( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Send request. + return $this->get( + endpoint: 'webhooks', + args: $this->build_total_count_and_pagination_params( + include_total_count: $include_total_count, + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) + ); + } + /** * Creates a webhook that will be called based on the chosen event types. * @@ -1494,7 +1526,7 @@ public function destroy_broadcast(int $id) * * @since 1.0.0 * - * @see https://developers.convertkit.com/#create-a-webhook + * @see https://developers.convertkit.com/v4.html#create-a-webhook * * @throws \InvalidArgumentException If the event is not supported. * @@ -1506,6 +1538,8 @@ public function create_webhook(string $url, string $event, string $parameter = ' switch ($event) { case 'subscriber.subscriber_activate': case 'subscriber.subscriber_unsubscribe': + case 'subscriber.subscriber_bounce': + case 'subscriber.subscriber_complain': case 'purchase.purchase_create': $eventData = ['name' => $event]; break; @@ -1553,7 +1587,7 @@ public function create_webhook(string $url, string $event, string $parameter = ' // Send request. return $this->post( - 'automations/hooks', + 'webhooks', [ 'target_url' => $url, 'event' => $eventData, @@ -1564,17 +1598,17 @@ public function create_webhook(string $url, string $event, string $parameter = ' /** * Deletes an existing webhook. * - * @param integer $rule_id Rule ID. + * @param integer $id Webhook ID. * * @since 1.0.0 * - * @see https://developers.convertkit.com/#destroy-webhook + * @see https://developers.convertkit.com/v4.html#delete-a-webhook * * @return false|object */ - public function destroy_webhook(int $rule_id) + public function delete_webhook(int $id) { - return $this->delete(sprintf('automations/hooks/%s', $rule_id)); + return $this->delete(sprintf('webhooks/%s', $id)); } /** From b04f62812ab0d04ad2dec338a723ad4f1bcc062f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Mon, 1 Apr 2024 17:28:42 +0100 Subject: [PATCH 58/78] Started tests --- tests/ConvertKitAPITest.php | 125 ++++++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 19 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index e9bd2ec..d494928 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -3791,7 +3791,102 @@ public function testDestroyBroadcastWithInvalidBroadcastID() } /** - * Test that create_webhook() and destroy_webhook() works. + * Test that get_webhooks() returns the expected data. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetWebhooks() + { + $result = $this->api->get_webhooks(); + + // Assert webhooks and pagination exist. + $this->assertDataExists($result, 'webhooks'); + $this->assertPaginationExists($result); + } + + /** + * Test that get_webhooks() returns the expected data + * when the total count is included. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetWebhooksWithTotalCount() + { + $result = $this->api->get_webhooks( + include_total_count: true + ); + + // Assert webhooks and pagination exist. + $this->assertDataExists($result, 'webhooks'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); + } + + /** + * Test that get_webhooks() returns the expected data + * when pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetWebhooksPagination() + { + $result = $this->api->get_webhooks( + per_page: 1 + ); + + // Assert webhooks and pagination exist. + $this->assertDataExists($result, 'webhooks'); + $this->assertPaginationExists($result); + + // Assert a single webhook was returned. + $this->assertCount(1, $result->webhooks); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->get_webhooks( + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert webhooks and pagination exist. + $this->assertDataExists($result, 'webhooks'); + $this->assertPaginationExists($result); + + // Assert a single webhook was returned. + $this->assertCount(1, $result->webhooks); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_webhooks( + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert webhooks and pagination exist. + $this->assertDataExists($result, 'webhooks'); + $this->assertPaginationExists($result); + + // Assert a single webhook was returned. + $this->assertCount(1, $result->webhooks); + } + + /** + * Test that create_webhook() and delete_webhook() works. * * We do both, so we don't end up with unnecessary webhooks remaining * on the ConvertKit account when running tests. @@ -3800,10 +3895,8 @@ public function testDestroyBroadcastWithInvalidBroadcastID() * * @return void */ - public function testCreateAndDestroyWebhook() + public function testCreateAndDeleteWebhook() { - $this->markTestIncomplete(); - // Create a webhook first. $result = $this->api->create_webhook( url: 'https://webhook.site/9c731823-7e61-44c8-af39-43b11f700ecb', @@ -3811,13 +3904,13 @@ public function testCreateAndDestroyWebhook() ); $ruleID = $result->rule->id; - // Destroy the webhook. - $result = $this->api->destroy_webhook($ruleID); + // Delete the webhook. + $result = $this->api->delete_webhook($ruleID); $this->assertEquals($result->success, true); } /** - * Test that create_webhook() and destroy_webhook() works with an event parameter. + * Test that create_webhook() and delete_webhook() works with an event parameter. * * We do both, so we don't end up with unnecessary webhooks remaining * on the ConvertKit account when running tests. @@ -3826,10 +3919,8 @@ public function testCreateAndDestroyWebhook() * * @return void */ - public function testCreateAndDestroyWebhookWithEventParameter() + public function testCreateAndDeleteWebhookWithEventParameter() { - $this->markTestIncomplete(); - // Create a webhook first. $result = $this->api->create_webhook( url: 'https://webhook.site/9c731823-7e61-44c8-af39-43b11f700ecb', @@ -3838,8 +3929,8 @@ public function testCreateAndDestroyWebhookWithEventParameter() ); $ruleID = $result->rule->id; - // Destroy the webhook. - $result = $this->api->destroy_webhook($ruleID); + // Delete the webhook. + $result = $this->api->delete_webhook($ruleID); $this->assertEquals($result->success, true); } @@ -3853,8 +3944,6 @@ public function testCreateAndDestroyWebhookWithEventParameter() */ public function testCreateWebhookWithInvalidEvent() { - $this->markTestIncomplete(); - $this->expectException(InvalidArgumentException::class); $this->api->create_webhook( url: 'https://webhook.site/9c731823-7e61-44c8-af39-43b11f700ecb', @@ -3863,19 +3952,17 @@ public function testCreateWebhookWithInvalidEvent() } /** - * Test that destroy_webhook() throws a ClientException when an invalid + * Test that delete_webhook() throws a ClientException when an invalid * rule ID is specified. * * @since 1.0.0 * * @return void */ - public function testDestroyWebhookWithInvalidRuleID() + public function testsDeleteWebhookWithInvalidRuleID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); - $this->api->destroy_webhook(12345); + $this->api->delete_webhook(12345); } /** From b6e94c05a847c763d39a26cf5a61359e8a52bbce Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 2 Apr 2024 11:02:41 +0100 Subject: [PATCH 59/78] Use `type` parameter to fetch Forms or Landing Pages --- src/ConvertKit_API.php | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index f91831f..03ee994 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -411,6 +411,7 @@ public function get_forms( $result = $this->get( endpoint: 'forms', args: $this->build_total_count_and_pagination_params( + params: [ 'type' => 'embed' ], include_total_count: $include_total_count, after_cursor: $after_cursor, before_cursor: $before_cursor, @@ -418,16 +419,14 @@ public function get_forms( ) ); - // Exclude archived and landing pages. - foreach ($result->forms as $index => $form) { - // Exclude hosted forms i.e. landing pages. - if ($form->type === 'hosted') { - unset($result->forms[$index]); - continue; - } + // If archived results are included, return now. + if ($include_archived) { + return $result; + } - // Exclude archived forms, if required. - if (!$include_archived && isset($form->archived) && $form->archived) { + // Remove archived landing pages. + foreach ($result->forms as $index => $form) { + if (isset($form->archived) && $form->archived) { unset($result->forms[$index]); continue; } @@ -461,6 +460,7 @@ public function get_landing_pages( $result = $this->get( endpoint: 'forms', args: $this->build_total_count_and_pagination_params( + params: [ 'type' => 'hosted' ], include_total_count: $include_total_count, after_cursor: $after_cursor, before_cursor: $before_cursor, @@ -468,16 +468,14 @@ public function get_landing_pages( ) ); - // Exclude archived and non-hosted forms. - foreach ($result->forms as $index => $form) { - // Exclude non-hosted forms i.e. forms. - if ($form->type !== 'hosted') { - unset($result->forms[$index]); - continue; - } + // If archived results are included, return now. + if ($include_archived) { + return $result; + } - // Exclude archived forms, if required. - if (!$include_archived && isset($form->archived) && $form->archived) { + // Remove archived landing pages. + foreach ($result->forms as $index => $form) { + if (isset($form->archived) && $form->archived) { unset($result->forms[$index]); continue; } From bf449bf19b0a4bac23a368b80f22e3b6aee8e9d3 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 2 Apr 2024 11:43:35 +0100 Subject: [PATCH 60/78] Completed tests --- tests/ConvertKitAPITest.php | 135 ++++++++++++++++++++---------------- 1 file changed, 76 insertions(+), 59 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index d494928..42563cd 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -51,6 +51,15 @@ class ConvertKitAPITest extends TestCase */ protected $subscriber_ids = []; + /** + * Webhook IDs to delete on teardown of a test. + * + * @since 2.0.0 + * + * @var array + */ + protected $webhook_ids = []; + /** * Load .env configuration into $_ENV superglobal, and initialize the API * class before each test. @@ -97,6 +106,11 @@ protected function tearDown(): void foreach ($this->subscriber_ids as $id) { $this->api->unsubscribe($id); } + + // Delete any Webhooks. + foreach ($this->webhook_ids as $id) { + $this->api->delete_webhook($id); + } } /** @@ -3790,45 +3804,6 @@ public function testDestroyBroadcastWithInvalidBroadcastID() $this->api->destroy_broadcast(12345); } - /** - * Test that get_webhooks() returns the expected data. - * - * @since 2.0.0 - * - * @return void - */ - public function testGetWebhooks() - { - $result = $this->api->get_webhooks(); - - // Assert webhooks and pagination exist. - $this->assertDataExists($result, 'webhooks'); - $this->assertPaginationExists($result); - } - - /** - * Test that get_webhooks() returns the expected data - * when the total count is included. - * - * @since 2.0.0 - * - * @return void - */ - public function testGetWebhooksWithTotalCount() - { - $result = $this->api->get_webhooks( - include_total_count: true - ); - - // Assert webhooks and pagination exist. - $this->assertDataExists($result, 'webhooks'); - $this->assertPaginationExists($result); - - // Assert total count is included. - $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); - $this->assertGreaterThan(0, $result->pagination->total_count); - } - /** * Test that get_webhooks() returns the expected data * when pagination parameters and per_page limits are specified. @@ -3839,6 +3814,25 @@ public function testGetWebhooksWithTotalCount() */ public function testGetWebhooksPagination() { + // Create webhooks first. + $results = [ + $this->api->create_webhook( + url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'), + event: 'subscriber.subscriber_activate', + ), + $this->api->create_webhook( + url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'), + event: 'subscriber.subscriber_activate', + ), + ]; + + // Set webhook_ids to ensure webhooks are deleted after test. + $this->webhook_ids = [ + $results[0]->webhook->id, + $results[1]->webhook->id, + ]; + + // Get webhooks. $result = $this->api->get_webhooks( per_page: 1 ); @@ -3869,7 +3863,7 @@ public function testGetWebhooksPagination() // Assert has_previous_page and has_next_page are correct. $this->assertTrue($result->pagination->has_previous_page); - $this->assertTrue($result->pagination->has_next_page); + $this->assertFalse($result->pagination->has_next_page); // Use pagination to fetch previous page. $result = $this->api->get_webhooks( @@ -3886,7 +3880,7 @@ public function testGetWebhooksPagination() } /** - * Test that create_webhook() and delete_webhook() works. + * Test that create_webhook(), get_webhooks() and delete_webhook() works. * * We do both, so we don't end up with unnecessary webhooks remaining * on the ConvertKit account when running tests. @@ -3895,43 +3889,66 @@ public function testGetWebhooksPagination() * * @return void */ - public function testCreateAndDeleteWebhook() + public function testCreateGetAndDeleteWebhook() { // Create a webhook first. $result = $this->api->create_webhook( - url: 'https://webhook.site/9c731823-7e61-44c8-af39-43b11f700ecb', + url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'), event: 'subscriber.subscriber_activate', ); - $ruleID = $result->rule->id; + $id = $result->webhook->id; + + // Get webhooks. + $result = $this->api->get_webhooks(); + + // Assert webhooks and pagination exist. + $this->assertDataExists($result, 'webhooks'); + $this->assertPaginationExists($result); + + // Get webhooks including total count. + $result = $this->api->get_webhooks( + include_total_count: true + ); + + // Assert webhooks and pagination exist. + $this->assertDataExists($result, 'webhooks'); + $this->assertPaginationExists($result); + + // Assert total count is included. + $this->assertArrayHasKey('total_count', get_object_vars($result->pagination)); + $this->assertGreaterThan(0, $result->pagination->total_count); // Delete the webhook. - $result = $this->api->delete_webhook($ruleID); - $this->assertEquals($result->success, true); + $result = $this->api->delete_webhook($id); } /** - * Test that create_webhook() and delete_webhook() works with an event parameter. - * - * We do both, so we don't end up with unnecessary webhooks remaining - * on the ConvertKit account when running tests. + * Test that create_webhook() works with an event parameter. * * @since 1.0.0 * * @return void */ - public function testCreateAndDeleteWebhookWithEventParameter() + public function testCreateWebhookWithEventParameter() { - // Create a webhook first. + // Create a webhook. + $url = 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'); $result = $this->api->create_webhook( - url: 'https://webhook.site/9c731823-7e61-44c8-af39-43b11f700ecb', + url: $url, event: 'subscriber.form_subscribe', parameter: $_ENV['CONVERTKIT_API_FORM_ID'] ); - $ruleID = $result->rule->id; + + // Confirm webhook created with correct data. + $this->assertArrayHasKey('webhook', get_object_vars($result)); + $this->assertArrayHasKey('id', get_object_vars($result->webhook)); + $this->assertArrayHasKey('target_url', get_object_vars($result->webhook)); + $this->assertEquals($result->webhook->target_url, $url); + $this->assertEquals($result->webhook->event->name, 'form_subscribe'); + $this->assertEquals($result->webhook->event->form_id, $_ENV['CONVERTKIT_API_FORM_ID']); // Delete the webhook. - $result = $this->api->delete_webhook($ruleID); - $this->assertEquals($result->success, true); + $result = $this->api->delete_webhook($result->webhook->id); } /** @@ -3946,20 +3963,20 @@ public function testCreateWebhookWithInvalidEvent() { $this->expectException(InvalidArgumentException::class); $this->api->create_webhook( - url: 'https://webhook.site/9c731823-7e61-44c8-af39-43b11f700ecb', + url: 'https://webhook.site/' . str_shuffle('wfervdrtgsdewrafvwefds'), event: 'invalid.event' ); } /** * Test that delete_webhook() throws a ClientException when an invalid - * rule ID is specified. + * ID is specified. * * @since 1.0.0 * * @return void */ - public function testsDeleteWebhookWithInvalidRuleID() + public function testDeleteWebhookWithInvalidID() { $this->expectException(ClientException::class); $this->api->delete_webhook(12345); From 69c4cc9d152ad792d05974e3d29f25b9af2ae408 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 2 Apr 2024 12:29:40 +0100 Subject: [PATCH 61/78] Create subscriber when subscribing to resource (form, tag, sequence) --- tests/ConvertKitAPITest.php | 56 +++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 8 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index e9bd2ec..b25fc55 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -1134,16 +1134,26 @@ public function testGetSequencesPagination() */ public function testAddSubscriberToSequenceByEmail() { + // Create subscriber. + $emailAddress = $this->generateEmailAddress(); + $subscriber = $this->api->create_subscriber( + email_address: $emailAddress + ); + + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $subscriber->subscriber->id; + + // Add subscriber to sequence. $result = $this->api->add_subscriber_to_sequence_by_email( sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + email_address: $emailAddress ); $this->assertInstanceOf('stdClass', $result); $this->assertArrayHasKey('subscriber', get_object_vars($result)); $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); $this->assertEquals( get_object_vars($result->subscriber)['email_address'], - $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + $emailAddress ); } @@ -1190,14 +1200,23 @@ public function testAddSubscriberToSequenceByEmailWithInvalidEmailAddress() */ public function testAddSubscriberToSequence() { + // Create subscriber. + $subscriber = $this->api->create_subscriber( + email_address: $this->generateEmailAddress() + ); + + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $subscriber->subscriber->id; + + // Add subscriber to sequence. $result = $this->api->add_subscriber_to_sequence( sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] + subscriber_id: $subscriber->subscriber->id ); $this->assertInstanceOf('stdClass', $result); $this->assertArrayHasKey('subscriber', get_object_vars($result)); $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); - $this->assertEquals(get_object_vars($result->subscriber)['id'], $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); + $this->assertEquals(get_object_vars($result->subscriber)['id'], $subscriber->subscriber->id); } /** @@ -2388,14 +2407,27 @@ public function testGetResourcesInvalidResourceType() */ public function testAddSubscriberToFormByEmail() { + // Create subscriber. + $emailAddress = $this->generateEmailAddress(); + $subscriber = $this->api->create_subscriber( + email_address: $emailAddress + ); + + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $subscriber->subscriber->id; + + // Add subscriber to form. $result = $this->api->add_subscriber_to_form_by_email( form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'], - email_address: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] + email_address: $emailAddress ); $this->assertInstanceOf('stdClass', $result); $this->assertArrayHasKey('subscriber', get_object_vars($result)); $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); - $this->assertEquals(get_object_vars($result->subscriber)['id'], $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); + $this->assertEquals( + get_object_vars($result->subscriber)['email_address'], + $emailAddress + ); } /** @@ -2441,14 +2473,22 @@ public function testAddSubscriberToFormByEmailWithInvalidEmailAddress() */ public function testAddSubscriberToForm() { + // Create subscriber. + $subscriber = $this->api->create_subscriber( + email_address: $this->generateEmailAddress() + ); + + // Set subscriber_id to ensure subscriber is unsubscribed after test. + $this->subscriber_ids[] = $subscriber->subscriber->id; + $result = $this->api->add_subscriber_to_form( form_id: (int) $_ENV['CONVERTKIT_API_FORM_ID'], - subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] + subscriber_id: $subscriber->subscriber->id ); $this->assertInstanceOf('stdClass', $result); $this->assertArrayHasKey('subscriber', get_object_vars($result)); $this->assertArrayHasKey('id', get_object_vars($result->subscriber)); - $this->assertEquals(get_object_vars($result->subscriber)['id'], $_ENV['CONVERTKIT_API_SUBSCRIBER_ID']); + $this->assertEquals(get_object_vars($result->subscriber)['id'], $subscriber->subscriber->id); } /** From 87e871ac1c66d52a644326ec3f552e098549a5b9 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 2 Apr 2024 19:06:56 +0100 Subject: [PATCH 62/78] Completed tests for `type` filtering --- src/ConvertKit_API.php | 39 ++------------------ tests/ConvertKitAPITest.php | 72 +++++++++++++++++++++++++++++++++---- 2 files changed, 67 insertions(+), 44 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 03ee994..94ff38f 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -388,7 +388,6 @@ public function get_growth_stats(\DateTime $starting = null, \DateTime $ending = /** * Get forms. * - * @param boolean $include_archived To include archived forms in the response, use true. * @param boolean $include_total_count To include the total count of records in the response, use true. * @param string $after_cursor Return results after the given pagination cursor. * @param string $before_cursor Return results before the given pagination cursor. @@ -401,14 +400,12 @@ public function get_growth_stats(\DateTime $starting = null, \DateTime $ending = * @return false|array */ public function get_forms( - bool $include_archived = false, bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', int $per_page = 100 ) { - // Get forms and landing pages. - $result = $this->get( + return $this->get( endpoint: 'forms', args: $this->build_total_count_and_pagination_params( params: [ 'type' => 'embed' ], @@ -418,27 +415,11 @@ public function get_forms( per_page: $per_page ) ); - - // If archived results are included, return now. - if ($include_archived) { - return $result; - } - - // Remove archived landing pages. - foreach ($result->forms as $index => $form) { - if (isset($form->archived) && $form->archived) { - unset($result->forms[$index]); - continue; - } - } - - return $result; } /** * Get landing pages. * - * @param boolean $include_archived To include archived landing pages in the response, use true. * @param boolean $include_total_count To include the total count of records in the response, use true. * @param string $after_cursor Return results after the given pagination cursor. * @param string $before_cursor Return results before the given pagination cursor. @@ -451,13 +432,12 @@ public function get_forms( * @return false|array */ public function get_landing_pages( - bool $include_archived = false, bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', int $per_page = 100 ) { - $result = $this->get( + return $this->get( endpoint: 'forms', args: $this->build_total_count_and_pagination_params( params: [ 'type' => 'hosted' ], @@ -467,21 +447,6 @@ public function get_landing_pages( per_page: $per_page ) ); - - // If archived results are included, return now. - if ($include_archived) { - return $result; - } - - // Remove archived landing pages. - foreach ($result->forms as $index => $form) { - if (isset($form->archived) && $form->archived) { - unset($result->forms[$index]); - continue; - } - } - - return $result; } /** diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 7367bda..72b4420 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -716,8 +716,7 @@ public function testGetForms() $this->assertArrayHasKey('embed_url', $form); $this->assertArrayHasKey('archived', $form); - // Assert form isn't archived or a landing page. - $this->assertFalse($form['archived']); + // Assert form is not a landing page i.e embed. $this->assertEquals($form['type'], 'embed'); } } @@ -756,7 +755,7 @@ public function testGetFormsWithTotalCount() public function testGetFormsPagination() { $result = $this->api->get_forms( - per_page: 2 + per_page: 1 ); // Assert forms and pagination exist. @@ -772,7 +771,7 @@ public function testGetFormsPagination() // Use pagination to fetch next page. $result = $this->api->get_forms( - per_page: 2, + per_page: 1, after_cursor: $result->pagination->end_cursor ); @@ -789,7 +788,7 @@ public function testGetFormsPagination() // Use pagination to fetch previous page. $result = $this->api->get_forms( - per_page: 2, + per_page: 1, before_cursor: $result->pagination->start_cursor ); @@ -834,8 +833,7 @@ public function testGetLandingPages() $this->assertArrayHasKey('embed_url', $form); $this->assertArrayHasKey('archived', $form); - // Assert form isn't archived and is hosted i.e. landing page. - $this->assertFalse($form['archived']); + // Assert form is a landing page i.e. hosted. $this->assertEquals($form['type'], 'hosted'); } } @@ -863,6 +861,66 @@ public function testGetLandingPagesWithTotalCount() $this->assertGreaterThan(0, $result->pagination->total_count); } + /** + * Test that get_landing_pages() returns the expected data when pagination parameters + * and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetLandingPagesPagination() + { + $result = $this->api->get_landing_pages( + per_page: 1 + ); + + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Assert a single form was returned. + $this->assertCount(1, $result->forms); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + + // Use pagination to fetch next page. + $result = $this->api->get_landing_pages( + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Assert a single form was returned. + $this->assertCount(1, $result->forms); + + // Assert has_previous_page and has_next_page are correct. + $this->assertTrue($result->pagination->has_previous_page); + $this->assertFalse($result->pagination->has_next_page); + + // Use pagination to fetch previous page. + $result = $this->api->get_landing_pages( + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Assert a single form was returned. + $this->assertCount(1, $result->forms); + + // Assert has_previous_page and has_next_page are correct. + $this->assertFalse($result->pagination->has_previous_page); + $this->assertTrue($result->pagination->has_next_page); + } + /** * Test that get_form_subscriptions() returns the expected data * when a valid Form ID is specified. From 57bbb00ea10b01c0615c9f3ec8966b61399b7a81 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 4 Apr 2024 13:09:13 +0100 Subject: [PATCH 63/78] Updated `get_forms` and `get_landing_pages` to support new `status` filter --- src/ConvertKit_API.php | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 874a239..ed2652e 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -388,6 +388,7 @@ public function get_growth_stats(\DateTime $starting = null, \DateTime $ending = /** * Get forms. * + * @param string $status Form status (active|archived|trashed|all). * @param boolean $include_total_count To include the total count of records in the response, use true. * @param string $after_cursor Return results after the given pagination cursor. * @param string $before_cursor Return results before the given pagination cursor. @@ -400,6 +401,7 @@ public function get_growth_stats(\DateTime $starting = null, \DateTime $ending = * @return false|array */ public function get_forms( + string $status = 'active', bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', @@ -408,7 +410,10 @@ public function get_forms( return $this->get( endpoint: 'forms', args: $this->build_total_count_and_pagination_params( - params: [ 'type' => 'embed' ], + params: [ + 'type' => 'embed', + 'status' => $status, + ], include_total_count: $include_total_count, after_cursor: $after_cursor, before_cursor: $before_cursor, @@ -420,6 +425,7 @@ public function get_forms( /** * Get landing pages. * + * @param string $status Form status (active|archived|trashed|all). * @param boolean $include_total_count To include the total count of records in the response, use true. * @param string $after_cursor Return results after the given pagination cursor. * @param string $before_cursor Return results before the given pagination cursor. @@ -432,6 +438,7 @@ public function get_forms( * @return false|array */ public function get_landing_pages( + string $status = 'active', bool $include_total_count = false, string $after_cursor = '', string $before_cursor = '', @@ -440,7 +447,10 @@ public function get_landing_pages( return $this->get( endpoint: 'forms', args: $this->build_total_count_and_pagination_params( - params: [ 'type' => 'hosted' ], + params: [ + 'type' => 'hosted', + 'status' => $status, + ], include_total_count: $include_total_count, after_cursor: $after_cursor, before_cursor: $before_cursor, From 66cb9ae7b171217eedebe408fece1d0ed0c28ed0 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 4 Apr 2024 14:14:06 +0100 Subject: [PATCH 64/78] Completed tests --- src/ConvertKit_API.php | 8 ++--- tests/ConvertKitAPITest.php | 68 +++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index ed2652e..5b43cbe 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -410,8 +410,8 @@ public function get_forms( return $this->get( endpoint: 'forms', args: $this->build_total_count_and_pagination_params( - params: [ - 'type' => 'embed', + params: [ + 'type' => 'embed', 'status' => $status, ], include_total_count: $include_total_count, @@ -447,8 +447,8 @@ public function get_landing_pages( return $this->get( endpoint: 'forms', args: $this->build_total_count_and_pagination_params( - params: [ - 'type' => 'hosted', + params: [ + 'type' => 'hosted', 'status' => $status, ], include_total_count: $include_total_count, diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index ddb0f86..c123fd9 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -746,6 +746,49 @@ public function testGetForms() // Assert form is not a landing page i.e embed. $this->assertEquals($form['type'], 'embed'); + + // Assert form is not archived. + $this->assertFalse($form['archived']); + } + } + + /** + * Test that get_forms() returns the expected data when + * the status is set to archived. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetFormsWithArchivedStatus() + { + $result = $this->api->get_forms( + status: 'archived' + ); + + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Iterate through each form, confirming no landing pages were included. + foreach ($result->forms as $form) { + $form = get_object_vars($form); + + // Assert shape of object is valid. + $this->assertArrayHasKey('id', $form); + $this->assertArrayHasKey('name', $form); + $this->assertArrayHasKey('created_at', $form); + $this->assertArrayHasKey('type', $form); + $this->assertArrayHasKey('format', $form); + $this->assertArrayHasKey('embed_js', $form); + $this->assertArrayHasKey('embed_url', $form); + $this->assertArrayHasKey('archived', $form); + + // Assert form is not a landing page i.e embed. + $this->assertEquals($form['type'], 'embed'); + + // Assert form is not archived. + $this->assertTrue($form['archived']); } } @@ -863,9 +906,34 @@ public function testGetLandingPages() // Assert form is a landing page i.e. hosted. $this->assertEquals($form['type'], 'hosted'); + + // Assert form is not archived. + $this->assertFalse($form['archived']); } } + /** + * Test that get_landing_pages() returns the expected data when + * the status is set to archived. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetLandingPagesWithArchivedStatus() + { + $result = $this->api->get_forms( + status: 'archived' + ); + + // Assert forms and pagination exist. + $this->assertDataExists($result, 'forms'); + $this->assertPaginationExists($result); + + // Assert no landing pages are returned, as the account doesn't have any archived landing pages. + $this->assertCount(0, $result->forms); + } + /** * Test that get_landing_pages() returns the expected data * when the total count is included. From 68d149a9f9f84624235ece71c09df56e634c4587 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 4 Apr 2024 15:31:48 +0100 Subject: [PATCH 65/78] Set headers on `Request` object instead of `Client` This allows specific headers to be defined depending on the request i.e. an API request or fetching HTML. --- src/ConvertKit_API.php | 162 ++++++++--------------------------------- 1 file changed, 29 insertions(+), 133 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 173a193..f03f6b4 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -124,20 +124,8 @@ public function __construct( $this->access_token = $accessToken; $this->debug = $debug; - // Set headers. - $headers = [ - 'Accept' => 'application/json', - 'Content-Type' => 'application/json; charset=utf-8', - 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), - ]; - if (!empty($this->access_token)) { - $headers['Authorization'] = 'Bearer ' . $this->access_token; - } - // Set the Guzzle client. - $this->client = new Client( - ['headers' => $headers] - ); + $this->client = new Client(); if ($debug) { // If no debug log file location specified, define a default. @@ -243,6 +231,9 @@ public function get_access_token(string $authCode, string $redirectURI) $request = new Request( method: 'POST', uri: $this->oauth_token_url, + headers: [ + 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + ], body: (string) json_encode( [ 'code' => $authCode, @@ -278,6 +269,9 @@ public function refresh_token(string $refreshToken, string $redirectURI) $request = new Request( method: 'POST', uri: $this->oauth_token_url, + headers: [ + 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + ], body: (string) json_encode( [ 'refresh_token' => $refreshToken, @@ -883,121 +877,6 @@ public function get_email_templates( ); } - /** - * Gets a resource index - * Possible resources: forms, landing_pages, subscription_forms, tags - * - * GET /{$resource}/ - * - * @param string $resource Resource type. - * - * @throws \InvalidArgumentException If the resource argument is not a supported resource type. - * - * @return array API response - */ - public function get_resources(string $resource) - { - // Assign the resource to the request variable. - $request = $resource; - - // Landing pages are included in the /forms endpoint. - if ($resource === 'landing_pages') { - $request = 'forms'; - } - - // Fetch resources. - $resources = $this->get($request); - - $this->create_log(sprintf('%s response %s', $resource, json_encode($resources))); - - // Return a blank array if no resources exist. - if (!$resources) { - $this->create_log('No resources'); - return []; - } - - // Build array of resources. - $_resource = []; - switch ($resource) { - // Forms. - case 'forms': - // Bail if no forms are set. - if (!isset($resources->forms)) { - $this->create_log('No form resources'); - return []; - } - - // Build array of forms. - foreach ($resources->forms as $form) { - // Exclude archived forms. - if (isset($form->archived) && $form->archived) { - continue; - } - - // Exclude hosted forms. - if ($form->type === 'hosted') { - continue; - } - - $_resource[] = $form; - } - break; - - // Landing Pages. - case 'landing_pages': - // Bail if no landing pages are set. - if (!isset($resources->forms)) { - $this->create_log('No landing page resources'); - return []; - } - - foreach ($resources->forms as $form) { - // Exclude archived landing pages. - if (isset($form->archived) && $form->archived) { - continue; - } - - // Exclude non-hosted (i.e. forms). - if ($form->type !== 'hosted') { - continue; - } - - $_resource[] = $form; - } - break; - - // Subscription Forms. - case 'subscription_forms': - // Exclude archived subscription forms. - foreach ($resources as $mapping) { - if (isset($mapping->archived) && $mapping->archived) { - continue; - } - - $_resource[$mapping->id] = $mapping->form_id; - } - break; - - // Tags. - case 'tags': - // Bail if no tags are set. - if (!isset($resources->tags)) { - $this->create_log('No tag resources'); - return []; - } - - foreach ($resources->tags as $tag) { - $_resource[] = $tag; - } - break; - - default: - throw new \InvalidArgumentException('An unsupported resource was specified.'); - }//end switch - - return $_resource; - } - /** * List subscribers. * @@ -1920,8 +1799,9 @@ public function get_segments( * Get markup from ConvertKit for the provided $url. * * Supports legacy forms and legacy landing pages. + * * Forms and Landing Pages should be embedded using the supplied JS embed script in - * the API response when using get_resources(). + * the API response when using get_forms() or get_landing_pages(). * * @param string $url URL of HTML page. * @@ -1942,9 +1822,13 @@ public function get_resource(string $url) // Fetch the resource. $request = new Request( - 'GET', - $url, - ['Accept-Encoding' => 'gzip'] + method: 'GET', + uri: $url, + headers: [ + 'Accept' => 'text/html', + 'Content-Type' => 'text/html; charset=utf-8', + 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + ] ); $response = $this->client->send($request); @@ -2169,7 +2053,13 @@ public function make_request(string $endpoint, string $method, array $args = []) $request = new Request( method: $method, - uri: $url + uri: $url, + headers: [ + 'Authorization'=> 'Bearer ' . $this->access_token, + 'Accept' => 'application/json', + 'Content-Type' => 'application/json; charset=utf-8', + 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + ] ); break; @@ -2177,6 +2067,12 @@ public function make_request(string $endpoint, string $method, array $args = []) $request = new Request( method: $method, uri: $url, + headers: [ + 'Authorization'=> 'Bearer ' . $this->access_token, + 'Accept' => 'application/json', + 'Content-Type' => 'application/json; charset=utf-8', + 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + ], body: (string) json_encode($args), ); break; From 8fcdcc8097dcdfa574603278dc403b53ab39ca3c Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 4 Apr 2024 15:31:55 +0100 Subject: [PATCH 66/78] Run `testGetResource` tests --- tests/ConvertKitAPITest.php | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index ea78d5a..ef79766 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -4682,8 +4682,6 @@ public function testGetSegmentsPagination() */ public function testGetResourceLegacyForm() { - $this->markTestIncomplete(); - $markup = $this->api->get_resource($_ENV['CONVERTKIT_API_LEGACY_FORM_URL']); // Assert that the markup is HTML. @@ -4702,8 +4700,6 @@ public function testGetResourceLegacyForm() */ public function testGetResourceLandingPage() { - $this->markTestIncomplete(); - $markup = $this->api->get_resource($_ENV['CONVERTKIT_API_LANDING_PAGE_URL']); // Assert that the markup is HTML. @@ -4722,8 +4718,6 @@ public function testGetResourceLandingPage() */ public function testGetResourceLegacyLandingPage() { - $this->markTestIncomplete(); - $markup = $this->api->get_resource($_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_URL']); // Assert that the markup is HTML. @@ -4743,8 +4737,6 @@ public function testGetResourceLegacyLandingPage() */ public function testGetResourceInvalidURL() { - $this->markTestIncomplete(); - $this->expectException(InvalidArgumentException::class); $markup = $this->api->get_resource('not-a-url'); } @@ -4759,8 +4751,6 @@ public function testGetResourceInvalidURL() */ public function testGetResourceInaccessibleURL() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $markup = $this->api->get_resource('https://convertkit.com/a/url/that/does/not/exist'); } From 688f14ec93cb60f6b446a191e6d068f784830d23 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 4 Apr 2024 15:33:54 +0100 Subject: [PATCH 67/78] Coding standards --- src/ConvertKit_API.php | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index f03f6b4..2eadfd6 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -232,7 +232,7 @@ public function get_access_token(string $authCode, string $redirectURI) method: 'POST', uri: $this->oauth_token_url, headers: [ - 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), ], body: (string) json_encode( [ @@ -270,7 +270,7 @@ public function refresh_token(string $refreshToken, string $redirectURI) method: 'POST', uri: $this->oauth_token_url, headers: [ - 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), ], body: (string) json_encode( [ @@ -1799,7 +1799,7 @@ public function get_segments( * Get markup from ConvertKit for the provided $url. * * Supports legacy forms and legacy landing pages. - * + * * Forms and Landing Pages should be embedded using the supplied JS embed script in * the API response when using get_forms() or get_landing_pages(). * @@ -1825,7 +1825,7 @@ public function get_resource(string $url) method: 'GET', uri: $url, headers: [ - 'Accept' => 'text/html', + 'Accept' => 'text/html', 'Content-Type' => 'text/html; charset=utf-8', 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), ] @@ -2055,10 +2055,10 @@ public function make_request(string $endpoint, string $method, array $args = []) method: $method, uri: $url, headers: [ - 'Authorization'=> 'Bearer ' . $this->access_token, - 'Accept' => 'application/json', - 'Content-Type' => 'application/json; charset=utf-8', - 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + 'Authorization' => 'Bearer ' . $this->access_token, + 'Accept' => 'application/json', + 'Content-Type' => 'application/json; charset=utf-8', + 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), ] ); break; @@ -2068,15 +2068,15 @@ public function make_request(string $endpoint, string $method, array $args = []) method: $method, uri: $url, headers: [ - 'Authorization'=> 'Bearer ' . $this->access_token, - 'Accept' => 'application/json', - 'Content-Type' => 'application/json; charset=utf-8', - 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + 'Authorization' => 'Bearer ' . $this->access_token, + 'Accept' => 'application/json', + 'Content-Type' => 'application/json; charset=utf-8', + 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), ], body: (string) json_encode($args), ); break; - } + }//end switch // Send request. $this->response = $this->client->send( From 2bb2b372f65347b4e62f59edc661bd3ff5625eda Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 9 Apr 2024 14:15:20 +0100 Subject: [PATCH 68/78] Use helper methods for user agent and headers --- src/ConvertKit_API.php | 61 +++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 07bae5e..52e3bae 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -232,7 +232,7 @@ public function get_access_token(string $authCode, string $redirectURI) method: 'POST', uri: $this->oauth_token_url, headers: [ - 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + 'User-Agent' => $this->user_agent(), ], body: (string) json_encode( [ @@ -270,7 +270,7 @@ public function refresh_token(string $refreshToken, string $redirectURI) method: 'POST', uri: $this->oauth_token_url, headers: [ - 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), + 'User-Agent' => $this->user_agent(), ], body: (string) json_encode( [ @@ -1870,11 +1870,7 @@ public function get_resource(string $url) $request = new Request( method: 'GET', uri: $url, - headers: [ - 'Accept' => 'text/html', - 'Content-Type' => 'text/html; charset=utf-8', - 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), - ] + headers: $this->request_headers('text/html'), ); $response = $this->client->send($request); @@ -2100,26 +2096,16 @@ public function make_request(string $endpoint, string $method, array $args = []) $request = new Request( method: $method, uri: $url, - headers: [ - 'Authorization' => 'Bearer ' . $this->access_token, - 'Accept' => 'application/json', - 'Content-Type' => 'application/json; charset=utf-8', - 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), - ] + headers: $this->request_headers(), ); break; default: $request = new Request( - method: $method, - uri: $url, - headers: [ - 'Authorization' => 'Bearer ' . $this->access_token, - 'Accept' => 'application/json', - 'Content-Type' => 'application/json; charset=utf-8', - 'User-Agent' => 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(), - ], - body: (string) json_encode($args), + method: $method, + uri: $url, + headers: $this->request_headers(), + body: (string) json_encode($args), ); break; }//end switch @@ -2153,4 +2139,35 @@ public function getResponseInterface() { return $this->response; } + + /** + * Returns the headers to use in an API request. + * + * @param string $type Accept and Content-Type Headers. + * + * @since 2.0.0 + * + * @return array + */ + private function request_headers(string $type = 'application/json') + { + return [ + 'Authorization' => 'Bearer ' . $this->access_token, + 'Accept' => $type, + 'Content-Type' => $type . '; charset=utf-8', + 'User-Agent' => $this->user_agent(), + ]; + } + + /** + * Returns the user agent string to use in all HTTP requests. + * + * @since 2.0.0 + * + * @return string + */ + private function user_agent() + { + return 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(); + } } From fcb034f22b3eaa2765bb6c85bdc78adf71732d93 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 9 Apr 2024 14:45:41 +0100 Subject: [PATCH 69/78] Add `auth` parameter to `request_headers` method with tests --- src/ConvertKit_API.php | 40 +++++++++++------ tests/ConvertKitAPITest.php | 88 +++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 14 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 52e3bae..6c5e6eb 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -231,9 +231,9 @@ public function get_access_token(string $authCode, string $redirectURI) $request = new Request( method: 'POST', uri: $this->oauth_token_url, - headers: [ - 'User-Agent' => $this->user_agent(), - ], + headers: $this->request_headers( + auth: false + ), body: (string) json_encode( [ 'code' => $authCode, @@ -269,9 +269,9 @@ public function refresh_token(string $refreshToken, string $redirectURI) $request = new Request( method: 'POST', uri: $this->oauth_token_url, - headers: [ - 'User-Agent' => $this->user_agent(), - ], + headers: $this->request_headers( + auth: false + ), body: (string) json_encode( [ 'refresh_token' => $refreshToken, @@ -1870,7 +1870,10 @@ public function get_resource(string $url) $request = new Request( method: 'GET', uri: $url, - headers: $this->request_headers('text/html'), + headers: $this->request_headers( + type: 'text/html', + auth: false + ), ); $response = $this->client->send($request); @@ -2143,20 +2146,29 @@ public function getResponseInterface() /** * Returns the headers to use in an API request. * - * @param string $type Accept and Content-Type Headers. + * @param string $type Accept and Content-Type Headers. + * @param boolean $auth Include authorization header. * * @since 2.0.0 * * @return array */ - private function request_headers(string $type = 'application/json') + private function request_headers(string $type = 'application/json', bool $auth = true) { - return [ - 'Authorization' => 'Bearer ' . $this->access_token, - 'Accept' => $type, - 'Content-Type' => $type . '; charset=utf-8', - 'User-Agent' => $this->user_agent(), + $headers = [ + 'Accept' => $type, + 'Content-Type' => $type . '; charset=utf-8', + 'User-Agent' => $this->user_agent(), ]; + + // If no authorization header required, return now. + if (!$auth) { + return $headers; + } + + // Add authorization header and return. + $headers['Authorization'] = 'Bearer ' . $this->access_token; + return $headers; } /** diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index d64cfa4..2d95c73 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -318,6 +318,94 @@ public function testDebugDisabled() $this->assertEmpty($this->getLogFileContents()); } + /** + * Test that calling request_headers() returns the expected array of headers + * + * @since 2.0.0 + * + * @return void + */ + public function testRequestHeadersMethod() + { + $headers = $this->callPrivateMethod($this->api, 'request_headers', []); + $this->assertArrayHasKey('Accept', $headers); + $this->assertArrayHasKey('Content-Type', $headers); + $this->assertArrayHasKey('User-Agent', $headers); + $this->assertArrayHasKey('Authorization', $headers); + $this->assertEquals($headers['Accept'], 'application/json'); + $this->assertEquals($headers['Content-Type'], 'application/json; charset=utf-8'); + $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); + $this->assertEquals($headers['Authorization'], 'Bearer ' . $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN']); + } + + /** + * Test that calling request_headers() with a different `type` parameter + * returns the expected array of headers + * + * @since 2.0.0 + * + * @return void + */ + public function testRequestHeadersMethodWithType() + { + $headers = $this->callPrivateMethod($this->api, 'request_headers', [ + 'type' => 'text/html', + ]); + $this->assertArrayHasKey('Accept', $headers); + $this->assertArrayHasKey('Content-Type', $headers); + $this->assertArrayHasKey('User-Agent', $headers); + $this->assertArrayHasKey('Authorization', $headers); + $this->assertEquals($headers['Accept'], 'text/html'); + $this->assertEquals($headers['Content-Type'], 'text/html; charset=utf-8'); + $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); + $this->assertEquals($headers['Authorization'], 'Bearer ' . $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN']); + } + + /** + * Test that calling request_headers() with the `auth` parameter set to false + * returns the expected array of headers + * + * @since 2.0.0 + * + * @return void + */ + public function testRequestHeadersMethodWithAuthDisabled() + { + $headers = $this->callPrivateMethod($this->api, 'request_headers', [ + 'auth' => false, + ]); + $this->assertArrayHasKey('Accept', $headers); + $this->assertArrayHasKey('Content-Type', $headers); + $this->assertArrayHasKey('User-Agent', $headers); + $this->assertArrayNotHasKey('Authorization', $headers); + $this->assertEquals($headers['Accept'], 'application/json'); + $this->assertEquals($headers['Content-Type'], 'application/json; charset=utf-8'); + $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); + } + + /** + * Test that calling request_headers() with a different `type` parameter + * and the `auth` parameter set to false returns the expected array of headers + * + * @since 2.0.0 + * + * @return void + */ + public function testRequestHeadersMethodWithTypeAndAuthDisabled() + { + $headers = $this->callPrivateMethod($this->api, 'request_headers', [ + 'type' => 'text/html', + 'auth' => false, + ]); + $this->assertArrayHasKey('Accept', $headers); + $this->assertArrayHasKey('Content-Type', $headers); + $this->assertArrayHasKey('User-Agent', $headers); + $this->assertArrayNotHasKey('Authorization', $headers); + $this->assertEquals($headers['Accept'], 'text/html'); + $this->assertEquals($headers['Content-Type'], 'text/html; charset=utf-8'); + $this->assertEquals($headers['User-Agent'], 'ConvertKitPHPSDK/' . $this->api::VERSION . ';PHP/' . phpversion()); + } + /** * Test that get_oauth_url() returns the correct URL to begin the OAuth process. * From f6563c95cf2556f352644d98356738bac7631982 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 11 Apr 2024 14:48:19 +0100 Subject: [PATCH 70/78] First draft of migration guide --- MIGRATION.md | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 MIGRATION.md diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..9f3f47c --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,121 @@ +# Migrating from v1.x SDK (v3 API) to v2.x SDK (v4 API) + +Whilst every best effort is made to minimise the number of breaking changes, some breaking changes exist to ensure improved method naming conventions and compatibility with OAuth authentication and the v4 API. + +This guide is designed to cover changes that developers may need to make to their existing implementation when upgrading to the v2 SDK. + +## PHP Version + +The minimum supported PHP version is `8.0`. Users on older PHP versions should continue to use the v1 SDK. + +## Authentication + +Authentication is now via OAuth. Refer to the README file for an implementation guide. + +Initializing the `ConvertKit_API` class now accepts a `clientID`, `clientSecret` and `accessToken` in place of the existing `api_key` and `api_secret`: + +```php +$api = new \ConvertKit_API\ConvertKit_API( + clientID: '', + clientSecret: '', + accessToken: '' +); +``` + +## Pagination + +For list based endpoints which fetch data from the API (such as broadcasts, custom fields, subscribers, tags, email templates, forms, purchases etc.), cursor based pagination is used. The following parameters can be specified in the API methods: + +- `per_page`: Defines the number of results to return, with a maximum value of 100 +- `after_cursor`: When specified, returns the next page of results based on the current result's `pagination->end_cursor` value +- `before_cursor`: When specified, returns the previous page of results based on the current result's `pagination->start_cursor` value + +## Accounts + +- Added: `get_account_colors()` +- Added: `update_account_colors()` +- Added: `get_creator_profile()` +- Added: `get_email_stats()` +- Added: `get_growth_stats()` + +## Broadcasts + +- Updated: `get_broadcasts()` supports pagination +- Updated: `create_broadcast()`: +-- `email_layout_template` is now `email_template_id`. To fetch the ID of the account's email templates, refer to `get_email_templates()` +-- `preview_text` option added +-- `subscriber_filter` option added +- Updated: `update_broadcast()` +-- `email_layout_template` is now `email_template_id`. To fetch the ID of the account's email templates, refer to `get_email_templates()` +-- `preview_text` option added +-- `subscriber_filter` option added +- Changed: `destroy_broadcast()` is renamed to `delete_broadcast()` + +## Custom Fields + +- Added: `create_custom_fields()` to create multiple custom fields in a single request +- Updated: `get_custom_fields()` supports pagination + +## Subscribers + +- Added: `create_subscriber()`. The concept of creating a subscriber via a form, tag or sequence is replaced with this new method. The subscriber can then be subscribed to resources (forms, tag, sequences) as necessary. +- Added: `create_subscribers()` to create multiple subscribers in a single request +- Added: `get_subscribers()` +- Changed: `unsubscribe()` is now `unsubscribe_by_email()`. Use `unsubscribe()` for unsubscribing by a subscriber ID +- Updated: `get_subscriber_tags()` supports pagination + +## Tags + +- Added: `create_tags()` to create multiple tags in a single request +- Updated: `get_tags()` supports pagination +- Updated: `get_tag_subscriptions()`: +-- supports pagination +-- supports filtering by subscribers by dates, covering `created_after`, `created_before`, `tagged_after` and `tagged_before` +-- `sort_order` is no longer supported +- Changed: `tag_subscriber()` is now `tag_subscriber_by_email()`. Use `tag_subscriber()` for tagging by subscriber ID + +## Email Templates + +- Added: `get_email_templates()` + +## Forms + +- Updated: `get_forms()`: +-- supports pagination +-- only returns active forms by default. Use the `status` parameter to filter by `active`, `archived`, `trashed` or `all` +- Updated: `get_landing_pages()`: +-- supports pagination +-- only returns active landing pages by default. Use the `status` parameter to filter by `active`, `archived`, `trashed` or `all` +- Updated: `get_form_subscriptions()`: +-- supports pagination +-- supports filtering by subscribers by dates, covering `created_after`, `created_before`, `added_after` and `added_before` +-- `sort_order` is no longer supported +- Changed: `add_subscriber_to_form()` is now `add_subscriber_to_form_by_email()`. Use `add_subscriber_to_form()` for adding subscriber to form by subscriber ID + +## Purchases + +- Updated: `create_purchase()` now supports named parameters for purchase data, instead of an `$options` array +- Changed: `list_purchases()` is now `get_purchases()`, with pagination support + +## Segments + +- Added: `get_segments()` + +## Sequences + +- Changed: `add_subscriber_to_sequence()` is now `add_subscriber_to_sequence_by_email()`. Use `add_subscriber_to_sequence()` for adding a subscriber to a sequence by subscriber ID +- Updated: `get_sequences()` supports pagination +- Updated: `get_sequence_subscriptions()`: +-- supports pagination +-- supports filtering by subscribers by dates, covering `created_after`, `created_before`, `added_after` and `added_before` +-- `sort_order` is no longer supported + +## Webhooks + +- Added: `get_webhooks()` +- Changed: `destroy_webhook()` is now `delete_webhook()` + +## Other + +- Removed: `form_subscribe()` was previously deprecated. Use `add_subscriber_to_form()` or `add_subscriber_to_form_by_email()` +- Removed: `add_tag()` was previously deprecated. Use `tag_subscriber()` or `tag_subscriber_by_email()` \ No newline at end of file From f327145b4f886ffa92a2f2ab5891e6be143b975a Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 11 Apr 2024 14:48:41 +0100 Subject: [PATCH 71/78] Update wording on how to register an OAuth application --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4579b3d..78ec3b8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +== README.MD + # ConvertKit SDK PHP The ConvertKit PHP SDK provides convinient access to the ConvertKit API from applications written in the PHP language. @@ -39,7 +41,9 @@ If you use Composer, these dependencies should be handled automatically. ### 2.x (v4 API, OAuth, PHP 8.0+) -Please reach out to ConvertKit to set up an OAuth application for you. We'll provide you with your Client ID and Secret. +First, register your OAuth application in the `OAuth Applications` section at https://app.convertkit.com/account_settings/advanced_settings. + +Using the supplied Client ID and secret, redirect the user to ConvertKit to grant your application access to their ConvertKit account. ```php // Require the autoloader (if you're using a PHP framework, this may already be done for you). @@ -50,11 +54,8 @@ $api = new \ConvertKit_API\ConvertKit_API( clientID: '', clientSecret: '' ); -``` -To begin the OAuth process, redirect the user to ConvertKit to grant your application access to their ConvertKit account. - -```php +// Redirect to begin the OAuth process. header('Location: '.$api->get_oauth_url('')); ``` From fb4759478589bbdc2b1bb491a1e3ef245d2e5b7f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 11 Apr 2024 14:53:17 +0100 Subject: [PATCH 72/78] Added links between readme and migration guide --- MIGRATION.md | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 9f3f47c..6d6e830 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -10,7 +10,7 @@ The minimum supported PHP version is `8.0`. Users on older PHP versions should ## Authentication -Authentication is now via OAuth. Refer to the README file for an implementation guide. +Authentication is now via OAuth. It's recommended to refer to the README file's [`Getting Started`](README.md#2x-v4-api-oauth-php-80) section for implementation. Initializing the `ConvertKit_API` class now accepts a `clientID`, `clientSecret` and `accessToken` in place of the existing `api_key` and `api_secret`: diff --git a/README.md b/README.md index 78ec3b8..b2bd177 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -== README.MD - # ConvertKit SDK PHP The ConvertKit PHP SDK provides convinient access to the ConvertKit API from applications written in the PHP language. @@ -13,6 +11,8 @@ It includes a pre-defined set of methods for interacting with the API. | 1.x | v3 | API Key and Secret | 7.4+ | | 2.x | v4 | OAuth | 8.0+ | +Refer to [this guide](MIGRATION.md) for changes when upgrading to the v2 SDK. + ## Composer You can install this PHP SDK via [Composer](http://getcomposer.org/). Run the following command: From c5ac174ac8230229227ac231d0b3f4808b812aa4 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 11 Apr 2024 14:55:46 +0100 Subject: [PATCH 73/78] Fixed indenting of sub bullet points --- MIGRATION.md | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 6d6e830..0a9fadb 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -42,13 +42,13 @@ For list based endpoints which fetch data from the API (such as broadcasts, cust - Updated: `get_broadcasts()` supports pagination - Updated: `create_broadcast()`: --- `email_layout_template` is now `email_template_id`. To fetch the ID of the account's email templates, refer to `get_email_templates()` --- `preview_text` option added --- `subscriber_filter` option added + - `email_layout_template` is now `email_template_id`. To fetch the ID of the account's email templates, refer to `get_email_templates()` + - `preview_text` option added + - `subscriber_filter` option added - Updated: `update_broadcast()` --- `email_layout_template` is now `email_template_id`. To fetch the ID of the account's email templates, refer to `get_email_templates()` --- `preview_text` option added --- `subscriber_filter` option added + - `email_layout_template` is now `email_template_id`. To fetch the ID of the account's email templates, refer to `get_email_templates()` + - `preview_text` option added + - `subscriber_filter` option added - Changed: `destroy_broadcast()` is renamed to `delete_broadcast()` ## Custom Fields @@ -69,9 +69,9 @@ For list based endpoints which fetch data from the API (such as broadcasts, cust - Added: `create_tags()` to create multiple tags in a single request - Updated: `get_tags()` supports pagination - Updated: `get_tag_subscriptions()`: --- supports pagination --- supports filtering by subscribers by dates, covering `created_after`, `created_before`, `tagged_after` and `tagged_before` --- `sort_order` is no longer supported + - supports pagination + - supports filtering by subscribers by dates, covering `created_after`, `created_before`, `tagged_after` and `tagged_before` + - `sort_order` is no longer supported - Changed: `tag_subscriber()` is now `tag_subscriber_by_email()`. Use `tag_subscriber()` for tagging by subscriber ID ## Email Templates @@ -81,15 +81,15 @@ For list based endpoints which fetch data from the API (such as broadcasts, cust ## Forms - Updated: `get_forms()`: --- supports pagination --- only returns active forms by default. Use the `status` parameter to filter by `active`, `archived`, `trashed` or `all` + - supports pagination + - only returns active forms by default. Use the `status` parameter to filter by `active`, `archived`, `trashed` or `all` - Updated: `get_landing_pages()`: --- supports pagination --- only returns active landing pages by default. Use the `status` parameter to filter by `active`, `archived`, `trashed` or `all` + - supports pagination + - only returns active landing pages by default. Use the `status` parameter to filter by `active`, `archived`, `trashed` or `all` - Updated: `get_form_subscriptions()`: --- supports pagination --- supports filtering by subscribers by dates, covering `created_after`, `created_before`, `added_after` and `added_before` --- `sort_order` is no longer supported + - supports pagination + - supports filtering by subscribers by dates, covering `created_after`, `created_before`, `added_after` and `added_before` + - `sort_order` is no longer supported - Changed: `add_subscriber_to_form()` is now `add_subscriber_to_form_by_email()`. Use `add_subscriber_to_form()` for adding subscriber to form by subscriber ID ## Purchases @@ -106,9 +106,9 @@ For list based endpoints which fetch data from the API (such as broadcasts, cust - Changed: `add_subscriber_to_sequence()` is now `add_subscriber_to_sequence_by_email()`. Use `add_subscriber_to_sequence()` for adding a subscriber to a sequence by subscriber ID - Updated: `get_sequences()` supports pagination - Updated: `get_sequence_subscriptions()`: --- supports pagination --- supports filtering by subscribers by dates, covering `created_after`, `created_before`, `added_after` and `added_before` --- `sort_order` is no longer supported + - supports pagination + - supports filtering by subscribers by dates, covering `created_after`, `created_before`, `added_after` and `added_before` + - `sort_order` is no longer supported ## Webhooks From 3d22954f1020f04345c5a20f190cbd5cd2fd03aa Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 12 Apr 2024 18:12:43 +0100 Subject: [PATCH 74/78] Implement methods as traits for portability across multiple API clients --- src/ConvertKit_API.php | 2034 +++------------------------------ src/ConvertKit_API_Traits.php | 1813 +++++++++++++++++++++++++++++ 2 files changed, 1964 insertions(+), 1883 deletions(-) create mode 100644 src/ConvertKit_API_Traits.php diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 6c5e6eb..d77bd42 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -18,62 +18,8 @@ */ class ConvertKit_API { - /** - * The SDK version. - * - * @var string - */ - public const VERSION = '2.0.0'; - - /** - * ConvertKit OAuth Application Client ID - * - * @var string - */ - protected $client_id = ''; - - /** - * ConvertKit OAuth Application Client Secret - * - * @var string - */ - protected $client_secret = ''; - - /** - * Access Token - * - * @var string - */ - protected $access_token = ''; - - /** - * OAuth Authorization URL - * - * @var string - */ - protected $oauth_authorize_url = 'https://app.convertkit.com/oauth/authorize'; - - /** - * OAuth Token URL - * - * @var string - */ - protected $oauth_token_url = 'https://api.convertkit.com/oauth/token'; - - /** - * Version of ConvertKit API - * - * @var string - */ - protected $api_version = 'v4'; - - /** - * ConvertKit API URL - * - * @var string - */ - protected $api_url_base = 'https://api.convertkit.com/'; - + use ConvertKit_API_Traits; + /** * Debug * @@ -231,7 +177,7 @@ public function get_access_token(string $authCode, string $redirectURI) $request = new Request( method: 'POST', uri: $this->oauth_token_url, - headers: $this->request_headers( + headers: $this->get_request_headers( auth: false ), body: (string) json_encode( @@ -269,7 +215,7 @@ public function refresh_token(string $refreshToken, string $redirectURI) $request = new Request( method: 'POST', uri: $this->oauth_token_url, - headers: $this->request_headers( + headers: $this->get_request_headers( auth: false ), body: (string) json_encode( @@ -294,1881 +240,203 @@ public function refresh_token(string $refreshToken, string $redirectURI) } /** - * Gets the current account - * - * @see https://developers.convertkit.com/v4.html#get-current-account - * - * @return false|mixed - */ - public function get_account() - { - return $this->get('account'); - } - - /** - * Gets the account's colors + * Get markup from ConvertKit for the provided $url. * - * @see https://developers.convertkit.com/v4.html#list-colors + * Supports legacy forms and legacy landing pages. * - * @return false|mixed - */ - public function get_account_colors() - { - return $this->get('account/colors'); - } - - /** - * Gets the account's colors + * Forms and Landing Pages should be embedded using the supplied JS embed script in + * the API response when using get_forms() or get_landing_pages(). * - * @param array $colors Hex colors. + * @param string $url URL of HTML page. * - * @see https://developers.convertkit.com/v4.html#list-colors + * @throws \InvalidArgumentException If the URL is not a valid URL format. + * @throws \Exception If parsing the legacy form or landing page failed. * - * @return false|mixed + * @return false|string */ - public function update_account_colors(array $colors) + public function get_resource(string $url) { - return $this->put( - endpoint: 'account/colors', - args: ['colors' => $colors] - ); - } + if (!filter_var($url, FILTER_VALIDATE_URL)) { + throw new \InvalidArgumentException(); + } - /** - * Gets the Creator Profile - * - * @see https://developers.convertkit.com/v4.html#get-creator-profile - * - * @return false|mixed - */ - public function get_creator_profile() - { - return $this->get('account/creator_profile'); - } + $resource = ''; - /** - * Gets email stats - * - * @see https://developers.convertkit.com/v4.html#get-email-stats - * - * @return false|mixed - */ - public function get_email_stats() - { - return $this->get('account/email_stats'); - } + $this->create_log(sprintf('Getting resource %s', $url)); - /** - * Gets growth stats - * - * @param \DateTime $starting Gets stats for time period beginning on this date. Defaults to 90 days ago. - * @param \DateTime $ending Gets stats for time period ending on this date. Defaults to today. - * - * @see https://developers.convertkit.com/v4.html#get-growth-stats - * - * @return false|mixed - */ - public function get_growth_stats(\DateTime $starting = null, \DateTime $ending = null) - { - return $this->get( - 'account/growth_stats', - [ - 'starting' => (!is_null($starting) ? $starting->format('Y-m-d') : ''), - 'ending' => (!is_null($ending) ? $ending->format('Y-m-d') : ''), - ] + // Fetch the resource. + $request = new Request( + method: 'GET', + uri: $url, + headers: $this->get_request_headers( + type: 'text/html', + auth: false + ), ); - } + $response = $this->client->send($request); - /** - * Get forms. - * - * @param string $status Form status (active|archived|trashed|all). - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#convertkit-api-forms - * - * @return false|array - */ - public function get_forms( - string $status = 'active', - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - return $this->get( - endpoint: 'forms', - args: $this->build_total_count_and_pagination_params( - params: [ - 'type' => 'embed', - 'status' => $status, - ], - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } + // Fetch HTML. + $body = $response->getBody()->getContents(); - /** - * Get landing pages. - * - * @param string $status Form status (active|archived|trashed|all). - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#convertkit-api-forms - * - * @return false|array - */ - public function get_landing_pages( - string $status = 'active', - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - return $this->get( - endpoint: 'forms', - args: $this->build_total_count_and_pagination_params( - params: [ - 'type' => 'hosted', - 'status' => $status, - ], - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) + // Forcibly tell DOMDocument that this HTML uses the UTF-8 charset. + // isn't enough, as DOMDocument still interprets the HTML as ISO-8859, + // which breaks character encoding. + // Use of mb_convert_encoding() with HTML-ENTITIES is deprecated in PHP 8.2, so we have to use this method. + // If we don't, special characters render incorrectly. + $body = str_replace( + '', + '' . "\n" . '', + $body ); + + // Get just the scheme and host from the URL. + $url_scheme_host_only = parse_url($url, PHP_URL_SCHEME) . '://' . parse_url($url, PHP_URL_HOST); + + // Load the HTML into a DOMDocument. + libxml_use_internal_errors(true); + $html = new \DOMDocument(); + $html->loadHTML($body); + + // Convert any relative URLs to absolute URLs in the HTML DOM. + $this->convert_relative_to_absolute_urls($html->getElementsByTagName('a'), 'href', $url_scheme_host_only); + $this->convert_relative_to_absolute_urls($html->getElementsByTagName('link'), 'href', $url_scheme_host_only); + $this->convert_relative_to_absolute_urls($html->getElementsByTagName('img'), 'src', $url_scheme_host_only); + $this->convert_relative_to_absolute_urls($html->getElementsByTagName('script'), 'src', $url_scheme_host_only); + $this->convert_relative_to_absolute_urls($html->getElementsByTagName('form'), 'action', $url_scheme_host_only); + + // Save HTML. + $resource = $html->saveHTML(); + + // If the result is false, return a blank string. + if (!$resource) { + throw new \Exception(sprintf('Could not parse %s', $url)); + } + + // Remove some HTML tags that DOMDocument adds, returning the output. + // We do this instead of using LIBXML_HTML_NOIMPLIED in loadHTML(), because Legacy Forms + // are not always contained in a single root / outer element, which is required for + // LIBXML_HTML_NOIMPLIED to correctly work. + $resource = $this->strip_html_head_body_tags($resource); + + return $resource; } /** - * Adds a subscriber to a form by email address + * Performs an API request using Guzzle. * - * @param integer $form_id Form ID. - * @param string $email_address Email Address. + * @param string $endpoint API Endpoint. + * @param string $method Request method. + * @param array>> $args Request arguments. * - * @see https://developers.convertkit.com/v4.html#add-subscriber-to-form-by-email-address + * @throws \Exception If JSON encoding arguments failed. * * @return false|mixed */ - public function add_subscriber_to_form_by_email(int $form_id, string $email_address) + public function request(string $endpoint, string $method, array $args = []) { - return $this->post( - endpoint: sprintf('forms/%s/subscribers', $form_id), - args: ['email_address' => $email_address] + // Build URL. + $url = $this->api_url_base . $this->api_version . '/' . $endpoint; + + // Log request. + $this->create_log(sprintf('%s %s', $method, $endpoint)); + $this->create_log(sprintf('%s', json_encode($args))); + + // Build request. + switch ($method) { + case 'GET': + if ($args) { + $url .= '?' . http_build_query($args); + } + + $request = new Request( + method: $method, + uri: $url, + headers: $this->get_request_headers(), + ); + break; + + default: + $request = new Request( + method: $method, + uri: $url, + headers: $this->get_request_headers(), + body: (string) json_encode($args), + ); + break; + }//end switch + + // Send request. + $this->response = $this->client->send( + $request, + ['exceptions' => false] ); + + // Get response. + $response_body = $this->response->getBody()->getContents(); + + // Log response. + $this->create_log(sprintf('Response Status Code: %s', $this->response->getStatusCode())); + $this->create_log(sprintf('Response Body: %s', $response_body)); + $this->create_log('Finish request successfully'); + + // Return response. + return json_decode($response_body); } /** - * Adds a subscriber to a form by subscriber ID - * - * @param integer $form_id Form ID. - * @param integer $subscriber_id Subscriber ID. - * - * @see https://developers.convertkit.com/v4.html#add-subscriber-to-form + * Returns the response interface used for the last API request. * * @since 2.0.0 * - * @return false|mixed + * @return \Psr\Http\Message\ResponseInterface */ - public function add_subscriber_to_form(int $form_id, int $subscriber_id) + public function getResponseInterface() { - return $this->post(sprintf('forms/%s/subscribers/%s', $form_id, $subscriber_id)); + return $this->response; } /** - * List subscribers for a form + * Returns the headers to use in an API request. * - * @param integer $form_id Form ID. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. - * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @param string $type Accept and Content-Type Headers. + * @param boolean $auth Include authorization header. * - * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-form + * @since 2.0.0 * - * @return false|mixed + * @return array */ - public function get_form_subscriptions( - int $form_id, - string $subscriber_state = 'active', - \DateTime $created_after = null, - \DateTime $created_before = null, - \DateTime $added_after = null, - \DateTime $added_before = null, - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - // Build parameters. - $options = []; + public function get_request_headers(string $type = 'application/json', bool $auth = true) + { + $headers = [ + 'Accept' => $type, + 'Content-Type' => $type . '; charset=utf-8', + 'User-Agent' => $this->get_user_agent(), + ]; - if (!empty($subscriber_state)) { - $options['status'] = $subscriber_state; - } - if (!is_null($created_after)) { - $options['created_after'] = $created_after->format('Y-m-d'); - } - if (!is_null($created_before)) { - $options['created_before'] = $created_before->format('Y-m-d'); - } - if (!is_null($added_after)) { - $options['added_after'] = $added_after->format('Y-m-d'); - } - if (!is_null($added_before)) { - $options['added_before'] = $added_before->format('Y-m-d'); + // If no authorization header required, return now. + if (!$auth) { + return $headers; } - // Send request. - return $this->get( - endpoint: sprintf('forms/%s/subscribers', $form_id), - args: $this->build_total_count_and_pagination_params( - params: $options, - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); + // Add authorization header and return. + $headers['Authorization'] = 'Bearer ' . $this->access_token; + return $headers; } /** - * Gets sequences + * Returns the maximum amount of time to wait for + * a response to the request before exiting. * - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. + * @since 2.0.0 * - * @see https://developers.convertkit.com/v4.html#list-sequences - * - * @return false|mixed + * @return int Timeout, in seconds. */ - public function get_sequences( - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - return $this->get( - endpoint: 'sequences', - args: $this->build_total_count_and_pagination_params( - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } + public function get_timeout() { - /** - * Adds a subscriber to a sequence by email address - * - * @param integer $sequence_id Sequence ID. - * @param string $email_address Email Address. - * - * @see https://developers.convertkit.com/v4.html#add-subscriber-to-sequence-by-email-address - * - * @return false|mixed - */ - public function add_subscriber_to_sequence_by_email(int $sequence_id, string $email_address) - { - return $this->post( - endpoint: sprintf('sequences/%s/subscribers', $sequence_id), - args: ['email_address' => $email_address] - ); - } + $timeout = 10; - /** - * Adds a subscriber to a sequence by subscriber ID - * - * @param integer $sequence_id Sequence ID. - * @param integer $subscriber_id Subscriber ID. - * - * @see https://developers.convertkit.com/v4.html#add-subscriber-to-sequence - * - * @since 2.0.0 - * - * @return false|mixed - */ - public function add_subscriber_to_sequence(int $sequence_id, int $subscriber_id) - { - return $this->post(sprintf('sequences/%s/subscribers/%s', $sequence_id, $subscriber_id)); - } - - /** - * List subscribers for a sequence - * - * @param integer $sequence_id Sequence ID. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. - * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-sequence - * - * @return false|mixed - */ - public function get_sequence_subscriptions( - int $sequence_id, - string $subscriber_state = 'active', - \DateTime $created_after = null, - \DateTime $created_before = null, - \DateTime $added_after = null, - \DateTime $added_before = null, - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - // Build parameters. - $options = []; - - if (!empty($subscriber_state)) { - $options['status'] = $subscriber_state; - } - if (!is_null($created_after)) { - $options['created_after'] = $created_after->format('Y-m-d'); - } - if (!is_null($created_before)) { - $options['created_before'] = $created_before->format('Y-m-d'); - } - if (!is_null($added_after)) { - $options['added_after'] = $added_after->format('Y-m-d'); - } - if (!is_null($added_before)) { - $options['added_before'] = $added_before->format('Y-m-d'); - } - - // Send request. - return $this->get( - endpoint: sprintf('sequences/%s/subscribers', $sequence_id), - args: $this->build_total_count_and_pagination_params( - params: $options, - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * List tags. - * - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @see https://developers.convertkit.com/v4.html#list-tags - * - * @return false|array - */ - public function get_tags( - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - return $this->get( - endpoint: 'tags', - args: $this->build_total_count_and_pagination_params( - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * Creates a tag. - * - * @param string $tag Tag Name. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#create-a-tag - * - * @return false|mixed - */ - public function create_tag(string $tag) - { - return $this->post( - endpoint: 'tags', - args: ['name' => $tag] - ); - } - - /** - * Creates multiple tags. - * - * @param array $tags Tag Names. - * @param string $callback_url URL to notify for large batch size when async processing complete. - * - * @since 1.1.0 - * - * @see https://developers.convertkit.com/v4.html#bulk-create-tags - * - * @return false|mixed - */ - public function create_tags(array $tags, string $callback_url = '') - { - // Build parameters. - $options = [ - 'tags' => [], - ]; - foreach ($tags as $i => $tag) { - $options['tags'][] = [ - 'name' => (string) $tag, - ]; - } - - if (!empty($callback_url)) { - $options['callback_url'] = $callback_url; - } - - // Send request. - return $this->post( - endpoint: 'bulk/tags', - args: $options - ); - } - - /** - * Tags a subscriber with the given existing Tag. - * - * @param integer $tag_id Tag ID. - * @param string $email_address Email Address. - * - * @see https://developers.convertkit.com/v4.html#tag-a-subscriber-by-email-address - * - * @return false|mixed - */ - public function tag_subscriber_by_email(int $tag_id, string $email_address) - { - return $this->post( - endpoint: sprintf('tags/%s/subscribers', $tag_id), - args: ['email_address' => $email_address] - ); - } - - /** - * Tags a subscriber by subscriber ID with the given existing Tag. - * - * @param integer $tag_id Tag ID. - * @param integer $subscriber_id Subscriber ID. - * - * @see https://developers.convertkit.com/v4.html#tag-a-subscriber - * - * @return false|mixed - */ - public function tag_subscriber(int $tag_id, int $subscriber_id) - { - return $this->post(sprintf('tags/%s/subscribers/%s', $tag_id, $subscriber_id)); - } - - /** - * Removes a tag from a subscriber. - * - * @param integer $tag_id Tag ID. - * @param integer $subscriber_id Subscriber ID. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#remove-tag-from-subscriber - * - * @return false|mixed - */ - public function remove_tag_from_subscriber(int $tag_id, int $subscriber_id) - { - return $this->delete(sprintf('tags/%s/subscribers/%s', $tag_id, $subscriber_id)); - } - - /** - * Removes a tag from a subscriber by email address. - * - * @param integer $tag_id Tag ID. - * @param string $email_address Subscriber email address. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#remove-tag-from-subscriber-by-email-address - * - * @return false|mixed - */ - public function remove_tag_from_subscriber_by_email(int $tag_id, string $email_address) - { - return $this->delete( - sprintf('tags/%s/subscribers', $tag_id), - ['email_address' => $email_address] - ); - } - - /** - * List subscribers for a tag - * - * @param integer $tag_id Tag ID. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $tagged_after Filter subscribers who have been tagged after this date. - * @param \DateTime $tagged_before Filter subscribers who have been tagged before this date. - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-tag - * - * @return false|mixed - */ - public function get_tag_subscriptions( - int $tag_id, - string $subscriber_state = 'active', - \DateTime $created_after = null, - \DateTime $created_before = null, - \DateTime $tagged_after = null, - \DateTime $tagged_before = null, - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - // Build parameters. - $options = []; - - if (!empty($subscriber_state)) { - $options['status'] = $subscriber_state; - } - if (!is_null($created_after)) { - $options['created_after'] = $created_after->format('Y-m-d'); - } - if (!is_null($created_before)) { - $options['created_before'] = $created_before->format('Y-m-d'); - } - if (!is_null($tagged_after)) { - $options['tagged_after'] = $tagged_after->format('Y-m-d'); - } - if (!is_null($tagged_before)) { - $options['tagged_before'] = $tagged_before->format('Y-m-d'); - } - - // Send request. - return $this->get( - endpoint: sprintf('tags/%s/subscribers', $tag_id), - args: $this->build_total_count_and_pagination_params( - params: $options, - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * List email templates. - * - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @since 2.0.0 - * - * @see https://developers.convertkit.com/v4.html#convertkit-api-email-templates - * - * @return false|mixed - */ - public function get_email_templates( - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - // Send request. - return $this->get( - endpoint: 'email_templates', - args: $this->build_total_count_and_pagination_params( - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * List subscribers. - * - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param string $email_address Search susbcribers by email address. This is an exact match search. - * @param \DateTime $created_after Filter subscribers who have been created after this date. - * @param \DateTime $created_before Filter subscribers who have been created before this date. - * @param \DateTime $updated_after Filter subscribers who have been updated after this date. - * @param \DateTime $updated_before Filter subscribers who have been updated before this date. - * @param string $sort_field Sort Field (id|updated_at|cancelled_at). - * @param string $sort_order Sort Order (asc|desc). - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @since 2.0.0 - * - * @see https://developers.convertkit.com/v4.html#list-subscribers - * - * @return false|mixed - */ - public function get_subscribers( - string $subscriber_state = 'active', - string $email_address = '', - \DateTime $created_after = null, - \DateTime $created_before = null, - \DateTime $updated_after = null, - \DateTime $updated_before = null, - string $sort_field = 'id', - string $sort_order = 'desc', - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - // Build parameters. - $options = []; - - if (!empty($subscriber_state)) { - $options['status'] = $subscriber_state; - } - if (!empty($email_address)) { - $options['email_address'] = $email_address; - } - if (!is_null($created_after)) { - $options['created_after'] = $created_after->format('Y-m-d'); - } - if (!is_null($created_before)) { - $options['created_before'] = $created_before->format('Y-m-d'); - } - if (!is_null($updated_after)) { - $options['updated_after'] = $updated_after->format('Y-m-d'); - } - if (!is_null($updated_before)) { - $options['updated_before'] = $updated_before->format('Y-m-d'); - } - if (!empty($sort_field)) { - $options['sort_field'] = $sort_field; - } - if (!empty($sort_order)) { - $options['sort_order'] = $sort_order; - } - - // Send request. - return $this->get( - endpoint: 'subscribers', - args: $this->build_total_count_and_pagination_params( - params: $options, - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * Create a subscriber. - * - * Behaves as an upsert. If a subscriber with the provided email address does not exist, - * it creates one with the specified first name and state. If a subscriber with the provided - * email address already exists, it updates the first name. - * - * @param string $email_address Email Address. - * @param string $first_name First Name. - * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). - * @param array $fields Custom Fields. - * - * @since 2.0.0 - * - * @see https://developers.convertkit.com/v4.html#create-a-subscriber - * - * @return mixed - */ - public function create_subscriber( - string $email_address, - string $first_name = '', - string $subscriber_state = '', - array $fields = [] - ) { - // Build parameters. - $options = ['email_address' => $email_address]; - - if (!empty($first_name)) { - $options['first_name'] = $first_name; - } - if (!empty($subscriber_state)) { - $options['state'] = $subscriber_state; - } - if (count($fields)) { - $options['fields'] = $fields; - } - - // Send request. - return $this->post( - endpoint: 'subscribers', - args: $options - ); - } - - /** - * Create multiple subscribers. - * - * @param array> $subscribers Subscribers. - * @param string $callback_url URL to notify for large batch size when async processing complete. - * - * @since 2.0.0 - * - * @see https://developers.convertkit.com/v4.html#bulk-create-subscribers - * - * @return mixed - */ - public function create_subscribers(array $subscribers, string $callback_url = '') - { - // Build parameters. - $options = ['subscribers' => $subscribers]; - - if (!empty($callback_url)) { - $options['callback_url'] = $callback_url; - } - - // Send request. - return $this->post( - endpoint: 'bulk/subscribers', - args: $options - ); - } - - /** - * Get the ConvertKit subscriber ID associated with email address if it exists. - * Return false if subscriber not found. - * - * @param string $email_address Email Address. - * - * @throws \InvalidArgumentException If the email address is not a valid email format. - * - * @see https://developers.convertkit.com/v4.html#get-a-subscriber - * - * @return false|integer - */ - public function get_subscriber_id(string $email_address) - { - $subscribers = $this->get( - 'subscribers', - ['email_address' => $email_address] - ); - - if (!count($subscribers->subscribers)) { - $this->create_log('No subscribers'); - return false; - } - - // Return the subscriber's ID. - return $subscribers->subscribers[0]->id; - } - - /** - * Get subscriber by id - * - * @param integer $subscriber_id Subscriber ID. - * - * @see https://developers.convertkit.com/v4.html#get-a-subscriber - * - * @return false|integer - */ - public function get_subscriber(int $subscriber_id) - { - return $this->get(sprintf('subscribers/%s', $subscriber_id)); - } - - /** - * Updates the information for a single subscriber. - * - * @param integer $subscriber_id Existing Subscriber ID. - * @param string $first_name New First Name. - * @param string $email_address New Email Address. - * @param array $fields Updated Custom Fields. - * - * @see https://developers.convertkit.com/v4.html#update-a-subscriber - * - * @return false|mixed - */ - public function update_subscriber( - int $subscriber_id, - string $first_name = '', - string $email_address = '', - array $fields = [] - ) { - // Build parameters. - $options = []; - - if (!empty($first_name)) { - $options['first_name'] = $first_name; - } - if (!empty($email_address)) { - $options['email_address'] = $email_address; - } - if (!empty($fields)) { - $options['fields'] = $fields; - } - - // Send request. - return $this->put( - sprintf('subscribers/%s', $subscriber_id), - $options - ); - } - - /** - * Unsubscribe an email address. - * - * @param string $email_address Email Address. - * - * @see https://developers.convertkit.com/v4.html#unsubscribe-subscriber - * - * @return false|object - */ - public function unsubscribe_by_email(string $email_address) - { - return $this->post( - sprintf( - 'subscribers/%s/unsubscribe', - $this->get_subscriber_id($email_address) - ) - ); - } - - /** - * Unsubscribe the given subscriber ID. - * - * @param integer $subscriber_id Subscriber ID. - * - * @see https://developers.convertkit.com/v4.html#unsubscribe-subscriber - * - * @return false|object - */ - public function unsubscribe(int $subscriber_id) - { - return $this->post(sprintf('subscribers/%s/unsubscribe', $subscriber_id)); - } - - /** - * Get a list of the tags for a subscriber. - * - * @param integer $subscriber_id Subscriber ID. - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @see https://developers.convertkit.com/v4.html#list-tags-for-a-subscriber - * - * @return false|array - */ - public function get_subscriber_tags( - int $subscriber_id, - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - return $this->get( - endpoint: sprintf('subscribers/%s/tags', $subscriber_id), - args: $this->build_total_count_and_pagination_params( - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * List broadcasts. - * - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @see https://developers.convertkit.com/v4.html#list-broadcasts - * - * @return false|mixed - */ - public function get_broadcasts( - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - // Send request. - return $this->get( - endpoint: 'broadcasts', - args: $this->build_total_count_and_pagination_params( - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * Creates a broadcast. - * - * @param string $subject The broadcast email's subject. - * @param string $content The broadcast's email HTML content. - * @param string $description An internal description of this broadcast. - * @param boolean $public Specifies whether or not this is a public post. - * @param \DateTime $published_at Specifies the time that this post was published (applicable - * only to public posts). - * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create - * a draft broadcast. If set to a future time, this is the time that - * the broadcast will be scheduled to send. - * @param string $email_address Sending email address; leave blank to use your account's - * default sending email address. - * @param string $email_template_id ID of the email template to use; leave blank to use your - * account's default email template. - * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image - * (applicable only to public posts). - * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast - * post (applicable only to public posts). - * @param string $preview_text Specify the preview text of the email. - * @param array $subscriber_filter Filter subscriber(s) to send the email to. - * - * @see https://developers.convertkit.com/v4.html#create-a-broadcast - * - * @return false|object - */ - public function create_broadcast( - string $subject = '', - string $content = '', - string $description = '', - bool $public = false, - \DateTime $published_at = null, - \DateTime $send_at = null, - string $email_address = '', - string $email_template_id = '', - string $thumbnail_alt = '', - string $thumbnail_url = '', - string $preview_text = '', - array $subscriber_filter = [] - ) { - $options = [ - 'email_template_id' => $email_template_id, - 'email_address' => $email_address, - 'content' => $content, - 'description' => $description, - 'public' => $public, - 'published_at' => (!is_null($published_at) ? $published_at->format('Y-m-d H:i:s') : ''), - 'send_at' => (!is_null($send_at) ? $send_at->format('Y-m-d H:i:s') : ''), - 'thumbnail_alt' => $thumbnail_alt, - 'thumbnail_url' => $thumbnail_url, - 'preview_text' => $preview_text, - 'subject' => $subject, - ]; - if (count($subscriber_filter)) { - $options['subscriber_filter'] = $subscriber_filter; - } - - // Iterate through options, removing blank entries. - foreach ($options as $key => $value) { - if (is_string($value) && strlen($value) === 0) { - unset($options[$key]); - } - } - - // If the post isn't public, remove some options that don't apply. - if (!$public) { - unset($options['published_at'], $options['thumbnail_alt'], $options['thumbnail_url']); - } - - // Send request. - return $this->post( - endpoint: 'broadcasts', - args: $options - ); - } - - /** - * Retrieve a specific broadcast. - * - * @param integer $id Broadcast ID. - * - * @see https://developers.convertkit.com/v4.html#get-a-broadcast - * - * @return false|object - */ - public function get_broadcast(int $id) - { - return $this->get(sprintf('broadcasts/%s', $id)); - } - - /** - * Get the statistics (recipient count, open rate, click rate, unsubscribe count, - * total clicks, status, and send progress) for a specific broadcast. - * - * @param integer $id Broadcast ID. - * - * @see https://developers.convertkit.com/v4.html#get-stats - * - * @return false|object - */ - public function get_broadcast_stats(int $id) - { - return $this->get(sprintf('broadcasts/%s/stats', $id)); - } - - /** - * Updates a broadcast. - * - * @param integer $id Broadcast ID. - * @param string $subject The broadcast email's subject. - * @param string $content The broadcast's email HTML content. - * @param string $description An internal description of this broadcast. - * @param boolean $public Specifies whether or not this is a public post. - * @param \DateTime $published_at Specifies the time that this post was published (applicable - * only to public posts). - * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create - * a draft broadcast. If set to a future time, this is the time that - * the broadcast will be scheduled to send. - * @param string $email_address Sending email address; leave blank to use your account's - * default sending email address. - * @param string $email_template_id ID of the email template to use; leave blank to use your - * account's default email template. - * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image - * (applicable only to public posts). - * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast - * post (applicable only to public posts). - * @param string $preview_text Specify the preview text of the email. - * @param array $subscriber_filter Filter subscriber(s) to send the email to. - * - * @see https://developers.convertkit.com/#create-a-broadcast - * - * @return false|object - */ - public function update_broadcast( - int $id, - string $subject = '', - string $content = '', - string $description = '', - bool $public = false, - \DateTime $published_at = null, - \DateTime $send_at = null, - string $email_address = '', - string $email_template_id = '', - string $thumbnail_alt = '', - string $thumbnail_url = '', - string $preview_text = '', - array $subscriber_filter = [] - ) { - $options = [ - 'email_template_id' => $email_template_id, - 'email_address' => $email_address, - 'content' => $content, - 'description' => $description, - 'public' => $public, - 'published_at' => (!is_null($published_at) ? $published_at->format('Y-m-d H:i:s') : ''), - 'send_at' => (!is_null($send_at) ? $send_at->format('Y-m-d H:i:s') : ''), - 'thumbnail_alt' => $thumbnail_alt, - 'thumbnail_url' => $thumbnail_url, - 'preview_text' => $preview_text, - 'subject' => $subject, - ]; - if (count($subscriber_filter)) { - $options['subscriber_filter'] = $subscriber_filter; - } - - // Iterate through options, removing blank entries. - foreach ($options as $key => $value) { - if (is_string($value) && strlen($value) === 0) { - unset($options[$key]); - } - } - - // If the post isn't public, remove some options that don't apply. - if (!$public) { - unset($options['published_at'], $options['thumbnail_alt'], $options['thumbnail_url']); - } - - // Send request. - return $this->put( - endpoint: sprintf('broadcasts/%s', $id), - args: $options - ); - } - - /** - * Deletes an existing broadcast. - * - * @param integer $id Broadcast ID. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#delete-a-broadcast - * - * @return false|object - */ - public function delete_broadcast(int $id) - { - return $this->delete(sprintf('broadcasts/%s', $id)); - } - - /** - * List webhooks. - * - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @since 2.0.0 - * - * @see https://developers.convertkit.com/v4.html#list-webhooks - * - * @return false|mixed - */ - public function get_webhooks( - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - // Send request. - return $this->get( - endpoint: 'webhooks', - args: $this->build_total_count_and_pagination_params( - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * Creates a webhook that will be called based on the chosen event types. - * - * @param string $url URL to receive event. - * @param string $event Event to subscribe to. - * @param string $parameter Optional parameter depending on the event. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#create-a-webhook - * - * @throws \InvalidArgumentException If the event is not supported. - * - * @return false|object - */ - public function create_webhook(string $url, string $event, string $parameter = '') - { - // Depending on the event, build the required event array structure. - switch ($event) { - case 'subscriber.subscriber_activate': - case 'subscriber.subscriber_unsubscribe': - case 'subscriber.subscriber_bounce': - case 'subscriber.subscriber_complain': - case 'purchase.purchase_create': - $eventData = ['name' => $event]; - break; - - case 'subscriber.form_subscribe': - $eventData = [ - 'name' => $event, - 'form_id' => $parameter, - ]; - break; - - case 'subscriber.course_subscribe': - case 'subscriber.course_complete': - $eventData = [ - 'name' => $event, - 'course_id' => $parameter, - ]; - break; - - case 'subscriber.link_click': - $eventData = [ - 'name' => $event, - 'initiator_value' => $parameter, - ]; - break; - - case 'subscriber.product_purchase': - $eventData = [ - 'name' => $event, - 'product_id' => $parameter, - ]; - break; - - case 'subscriber.tag_add': - case 'subscriber.tag_remove': - $eventData = [ - 'name' => $event, - 'tag_id' => $parameter, - ]; - break; - - default: - throw new \InvalidArgumentException(sprintf('The event %s is not supported', $event)); - }//end switch - - // Send request. - return $this->post( - 'webhooks', - [ - 'target_url' => $url, - 'event' => $eventData, - ] - ); - } - - /** - * Deletes an existing webhook. - * - * @param integer $id Webhook ID. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#delete-a-webhook - * - * @return false|object - */ - public function delete_webhook(int $id) - { - return $this->delete(sprintf('webhooks/%s', $id)); - } - - /** - * List custom fields. - * - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#list-custom-fields - * - * @return false|mixed - */ - public function get_custom_fields( - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - // Send request. - return $this->get( - endpoint: 'custom_fields', - args: $this->build_total_count_and_pagination_params( - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * Creates a custom field. - * - * @param string $label Custom Field label. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#create-a-custom-field - * - * @return false|object - */ - public function create_custom_field(string $label) - { - return $this->post( - endpoint: 'custom_fields', - args: ['label' => $label] - ); - } - - /** - * Creates multiple custom fields. - * - * @param array $labels Custom Fields labels. - * @param string $callback_url URL to notify for large batch size when async processing complete. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#bulk-create-custom-fields - * - * @return false|object - */ - public function create_custom_fields(array $labels, string $callback_url = '') - { - // Build parameters. - $options = [ - 'custom_fields' => [], - ]; - foreach ($labels as $i => $label) { - $options['custom_fields'][] = [ - 'label' => (string) $label, - ]; - } - - if (!empty($callback_url)) { - $options['callback_url'] = $callback_url; - } - - // Send request. - return $this->post( - endpoint: 'bulk/custom_fields', - args: $options - ); - } - - /** - * Updates an existing custom field. - * - * @param integer $id Custom Field ID. - * @param string $label Updated Custom Field label. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#update-a-custom-field - * - * @return false|object - */ - public function update_custom_field(int $id, string $label) - { - return $this->put( - endpoint: sprintf('custom_fields/%s', $id), - args: ['label' => $label] - ); - } - - /** - * Deletes an existing custom field. - * - * @param integer $id Custom Field ID. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/#destroy-field - * - * @return false|object - */ - public function delete_custom_field(int $id) - { - return $this->delete(sprintf('custom_fields/%s', $id)); - } - - /** - * List purchases. - * - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @since 1.0.0 - * - * @see https://developers.convertkit.com/v4.html#list-purchases - * - * @return false|mixed - */ - public function get_purchases( - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - // Send request. - return $this->get( - endpoint: 'purchases', - args: $this->build_total_count_and_pagination_params( - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * Retuns a specific purchase. - * - * @param integer $purchase_id Purchase ID. - * - * @see https://developers.convertkit.com/v4.html#get-a-purchase - * - * @return false|object - */ - public function get_purchase(int $purchase_id) - { - return $this->get(sprintf('purchases/%s', $purchase_id)); - } - - /** - * Creates a purchase. - * - * @param string $email_address Email Address. - * @param string $transaction_id Transaction ID. - * @param array $products Products. - * @param string $currency ISO Currency Code. - * @param string $first_name First Name. - * @param string $status Order Status. - * @param float $subtotal Subtotal. - * @param float $tax Tax. - * @param float $shipping Shipping. - * @param float $discount Discount. - * @param float $total Total. - * @param \DateTime $transaction_time Transaction date and time. - * - * @see https://developers.convertkit.com/v4.html#create-a-purchase - * - * @return false|object - */ - public function create_purchase( - string $email_address, - string $transaction_id, - array $products, - string $currency = 'USD', - string $first_name = null, - string $status = null, - float $subtotal = 0, - float $tax = 0, - float $shipping = 0, - float $discount = 0, - float $total = 0, - \DateTime $transaction_time = null - ) { - // Build parameters. - $options = [ - // Required fields. - 'email_address' => $email_address, - 'transaction_id' => $transaction_id, - 'products' => $products, - 'currency' => $currency, // Required, but if not provided, API will default to USD. - - // Optional fields. - 'first_name' => $first_name, - 'status' => $status, - 'subtotal' => $subtotal, - 'tax' => $tax, - 'shipping' => $shipping, - 'discount' => $discount, - 'total' => $total, - 'transaction_time' => (!is_null($transaction_time) ? $transaction_time->format('Y-m-d H:i:s') : ''), - ]; - - // Iterate through options, removing blank and null entries. - foreach ($options as $key => $value) { - if (is_null($value)) { - unset($options[$key]); - continue; - } - - if (is_string($value) && strlen($value) === 0) { - unset($options[$key]); - } - } - - return $this->post('purchases', $options); - } - - /** - * List segments. - * - * @param boolean $include_total_count To include the total count of records in the response, use true. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @since 2.0.0 - * - * @see https://developers.convertkit.com/v4.html#convertkit-api-segments - * - * @return false|mixed - */ - public function get_segments( - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - // Send request. - return $this->get( - endpoint: 'segments', - args: $this->build_total_count_and_pagination_params( - include_total_count: $include_total_count, - after_cursor: $after_cursor, - before_cursor: $before_cursor, - per_page: $per_page - ) - ); - } - - /** - * Get markup from ConvertKit for the provided $url. - * - * Supports legacy forms and legacy landing pages. - * - * Forms and Landing Pages should be embedded using the supplied JS embed script in - * the API response when using get_forms() or get_landing_pages(). - * - * @param string $url URL of HTML page. - * - * @throws \InvalidArgumentException If the URL is not a valid URL format. - * @throws \Exception If parsing the legacy form or landing page failed. - * - * @return false|string - */ - public function get_resource(string $url) - { - if (!filter_var($url, FILTER_VALIDATE_URL)) { - throw new \InvalidArgumentException(); - } - - $resource = ''; - - $this->create_log(sprintf('Getting resource %s', $url)); - - // Fetch the resource. - $request = new Request( - method: 'GET', - uri: $url, - headers: $this->request_headers( - type: 'text/html', - auth: false - ), - ); - $response = $this->client->send($request); - - // Fetch HTML. - $body = $response->getBody()->getContents(); - - // Forcibly tell DOMDocument that this HTML uses the UTF-8 charset. - // isn't enough, as DOMDocument still interprets the HTML as ISO-8859, - // which breaks character encoding. - // Use of mb_convert_encoding() with HTML-ENTITIES is deprecated in PHP 8.2, so we have to use this method. - // If we don't, special characters render incorrectly. - $body = str_replace( - '', - '' . "\n" . '', - $body - ); - - // Get just the scheme and host from the URL. - $url_scheme_host_only = parse_url($url, PHP_URL_SCHEME) . '://' . parse_url($url, PHP_URL_HOST); - - // Load the HTML into a DOMDocument. - libxml_use_internal_errors(true); - $html = new \DOMDocument(); - $html->loadHTML($body); - - // Convert any relative URLs to absolute URLs in the HTML DOM. - $this->convert_relative_to_absolute_urls($html->getElementsByTagName('a'), 'href', $url_scheme_host_only); - $this->convert_relative_to_absolute_urls($html->getElementsByTagName('link'), 'href', $url_scheme_host_only); - $this->convert_relative_to_absolute_urls($html->getElementsByTagName('img'), 'src', $url_scheme_host_only); - $this->convert_relative_to_absolute_urls($html->getElementsByTagName('script'), 'src', $url_scheme_host_only); - $this->convert_relative_to_absolute_urls($html->getElementsByTagName('form'), 'action', $url_scheme_host_only); - - // Save HTML. - $resource = $html->saveHTML(); - - // If the result is false, return a blank string. - if (!$resource) { - throw new \Exception(sprintf('Could not parse %s', $url)); - } - - // Remove some HTML tags that DOMDocument adds, returning the output. - // We do this instead of using LIBXML_HTML_NOIMPLIED in loadHTML(), because Legacy Forms - // are not always contained in a single root / outer element, which is required for - // LIBXML_HTML_NOIMPLIED to correctly work. - $resource = $this->strip_html_head_body_tags($resource); - - return $resource; - } - - /** - * Converts any relative URls to absolute, fully qualified HTTP(s) URLs for the given - * DOM Elements. - * - * @param \DOMNodeList<\DOMElement> $elements Elements. - * @param string $attribute HTML Attribute. - * @param string $url Absolute URL to prepend to relative URLs. - * - * @since 1.0.0 - * - * @return void - */ - private function convert_relative_to_absolute_urls(\DOMNodeList $elements, string $attribute, string $url) // phpcs:ignore Squiz.Commenting.FunctionComment.IncorrectTypeHint, Generic.Files.LineLength.TooLong - { - // Anchor hrefs. - foreach ($elements as $element) { - // Skip if the attribute's value is empty. - if (empty($element->getAttribute($attribute))) { - continue; - } - - // Skip if the attribute's value is a fully qualified URL. - if (filter_var($element->getAttribute($attribute), FILTER_VALIDATE_URL)) { - continue; - } - - // Skip if this is a Google Font CSS URL. - if (strpos($element->getAttribute($attribute), '//fonts.googleapis.com') !== false) { - continue; - } - - // If here, the attribute's value is a relative URL, missing the http(s) and domain. - // Prepend the URL to the attribute's value. - $element->setAttribute($attribute, $url . $element->getAttribute($attribute)); - } - } - - /** - * Strips , and opening and closing tags from the given markup, - * as well as the Content-Type meta tag we might have added in get_html(). - * - * @param string $markup HTML Markup. - * - * @since 1.0.0 - * - * @return string HTML Markup - */ - private function strip_html_head_body_tags(string $markup) - { - $markup = str_replace('', '', $markup); - $markup = str_replace('', '', $markup); - $markup = str_replace('', '', $markup); - $markup = str_replace('', '', $markup); - $markup = str_replace('', '', $markup); - $markup = str_replace('', '', $markup); - $markup = str_replace('', '', $markup); - - return $markup; - } - - /** - * Adds total count and pagination parameters to the given array of existing API parameters. - * - * @param array $params API parameters. - * @param boolean $include_total_count Return total count of records. - * @param string $after_cursor Return results after the given pagination cursor. - * @param string $before_cursor Return results before the given pagination cursor. - * @param integer $per_page Number of results to return. - * - * @since 2.0.0 - * - * @return array - */ - private function build_total_count_and_pagination_params( - array $params = [], - bool $include_total_count = false, - string $after_cursor = '', - string $before_cursor = '', - int $per_page = 100 - ) { - $params['include_total_count'] = $include_total_count; - if (!empty($after_cursor)) { - $params['after'] = $after_cursor; - } - if (!empty($before_cursor)) { - $params['before'] = $before_cursor; - } - if (!empty($per_page)) { - $params['per_page'] = $per_page; - } - - return $params; - } - - /** - * Performs a GET request to the API. - * - * @param string $endpoint API Endpoint. - * @param array|string> $args Request arguments. - * - * @return false|mixed - */ - public function get(string $endpoint, array $args = []) - { - return $this->make_request($endpoint, 'GET', $args); - } - - /** - * Performs a POST request to the API. - * - * @param string $endpoint API Endpoint. - * @param array>> $args Request arguments. - * - * @return false|mixed - */ - public function post(string $endpoint, array $args = []) - { - return $this->make_request($endpoint, 'POST', $args); - } - - /** - * Performs a PUT request to the API. - * - * @param string $endpoint API Endpoint. - * @param array|string> $args Request arguments. - * - * @return false|mixed - */ - public function put(string $endpoint, array $args = []) - { - return $this->make_request($endpoint, 'PUT', $args); - } - - /** - * Performs a DELETE request to the API. - * - * @param string $endpoint API Endpoint. - * @param array|string> $args Request arguments. - * - * @return false|mixed - */ - public function delete(string $endpoint, array $args = []) - { - return $this->make_request($endpoint, 'DELETE', $args); - } - - /** - * Performs an API request using Guzzle. - * - * @param string $endpoint API Endpoint. - * @param string $method Request method. - * @param array>> $args Request arguments. - * - * @throws \Exception If JSON encoding arguments failed. - * - * @return false|mixed - */ - public function make_request(string $endpoint, string $method, array $args = []) - { - // Build URL. - $url = $this->api_url_base . $this->api_version . '/' . $endpoint; - - // Log request. - $this->create_log(sprintf('%s %s', $method, $endpoint)); - $this->create_log(sprintf('%s', json_encode($args))); - - // Build request. - switch ($method) { - case 'GET': - if ($args) { - $url .= '?' . http_build_query($args); - } - - $request = new Request( - method: $method, - uri: $url, - headers: $this->request_headers(), - ); - break; - - default: - $request = new Request( - method: $method, - uri: $url, - headers: $this->request_headers(), - body: (string) json_encode($args), - ); - break; - }//end switch - - // Send request. - $this->response = $this->client->send( - $request, - ['exceptions' => false] - ); - - // Get response. - $response_body = $this->response->getBody()->getContents(); - - // Log response. - $this->create_log(sprintf('Response Status Code: %s', $this->response->getStatusCode())); - $this->create_log(sprintf('Response Body: %s', $response_body)); - $this->create_log('Finish request successfully'); - - // Return response. - return json_decode($response_body); - } - - /** - * Returns the response interface used for the last API request. - * - * @since 2.0.0 - * - * @return \Psr\Http\Message\ResponseInterface - */ - public function getResponseInterface() - { - return $this->response; - } - - /** - * Returns the headers to use in an API request. - * - * @param string $type Accept and Content-Type Headers. - * @param boolean $auth Include authorization header. - * - * @since 2.0.0 - * - * @return array - */ - private function request_headers(string $type = 'application/json', bool $auth = true) - { - $headers = [ - 'Accept' => $type, - 'Content-Type' => $type . '; charset=utf-8', - 'User-Agent' => $this->user_agent(), - ]; + return $timeout; - // If no authorization header required, return now. - if (!$auth) { - return $headers; - } - - // Add authorization header and return. - $headers['Authorization'] = 'Bearer ' . $this->access_token; - return $headers; } /** @@ -2178,7 +446,7 @@ private function request_headers(string $type = 'application/json', bool $auth = * * @return string */ - private function user_agent() + public function get_user_agent() { return 'ConvertKitPHPSDK/' . self::VERSION . ';PHP/' . phpversion(); } diff --git a/src/ConvertKit_API_Traits.php b/src/ConvertKit_API_Traits.php new file mode 100644 index 0000000..d71f644 --- /dev/null +++ b/src/ConvertKit_API_Traits.php @@ -0,0 +1,1813 @@ +get('account'); + } + + /** + * Gets the account's colors + * + * @see https://developers.convertkit.com/v4.html#list-colors + * + * @return false|mixed + */ + public function get_account_colors() + { + return $this->get('account/colors'); + } + + /** + * Gets the account's colors + * + * @param array $colors Hex colors. + * + * @see https://developers.convertkit.com/v4.html#list-colors + * + * @return false|mixed + */ + public function update_account_colors(array $colors) + { + return $this->put( + 'account/colors', + ['colors' => $colors] + ); + } + + /** + * Gets the Creator Profile + * + * @see https://developers.convertkit.com/v4.html#get-creator-profile + * + * @return false|mixed + */ + public function get_creator_profile() + { + return $this->get('account/creator_profile'); + } + + /** + * Gets email stats + * + * @see https://developers.convertkit.com/v4.html#get-email-stats + * + * @return false|mixed + */ + public function get_email_stats() + { + return $this->get('account/email_stats'); + } + + /** + * Gets growth stats + * + * @param \DateTime $starting Gets stats for time period beginning on this date. Defaults to 90 days ago. + * @param \DateTime $ending Gets stats for time period ending on this date. Defaults to today. + * + * @see https://developers.convertkit.com/v4.html#get-growth-stats + * + * @return false|mixed + */ + public function get_growth_stats(\DateTime $starting = null, \DateTime $ending = null) + { + return $this->get( + 'account/growth_stats', + [ + 'starting' => (!is_null($starting) ? $starting->format('Y-m-d') : ''), + 'ending' => (!is_null($ending) ? $ending->format('Y-m-d') : ''), + ] + ); + } + + /** + * Get forms. + * + * @param string $status Form status (active|archived|trashed|all). + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#convertkit-api-forms + * + * @return false|array + */ + public function get_forms( + string $status = 'active', + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + return $this->get( + 'forms', + $this->build_total_count_and_pagination_params( + [ + 'type' => 'embed', + 'status' => $status, + ], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Get landing pages. + * + * @param string $status Form status (active|archived|trashed|all). + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#convertkit-api-forms + * + * @return false|array + */ + public function get_landing_pages( + string $status = 'active', + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + return $this->get( + 'forms', + $this->build_total_count_and_pagination_params( + [ + 'type' => 'hosted', + 'status' => $status, + ], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Adds a subscriber to a form by email address + * + * @param integer $form_id Form ID. + * @param string $email_address Email Address. + * + * @see https://developers.convertkit.com/v4.html#add-subscriber-to-form-by-email-address + * + * @return false|mixed + */ + public function add_subscriber_to_form_by_email(int $form_id, string $email_address) + { + return $this->post( + sprintf('forms/%s/subscribers', $form_id), + ['email_address' => $email_address] + ); + } + + /** + * Adds a subscriber to a form by subscriber ID + * + * @param integer $form_id Form ID. + * @param integer $subscriber_id Subscriber ID. + * + * @see https://developers.convertkit.com/v4.html#add-subscriber-to-form + * + * @since 2.0.0 + * + * @return false|mixed + */ + public function add_subscriber_to_form(int $form_id, int $subscriber_id) + { + return $this->post(sprintf('forms/%s/subscribers/%s', $form_id, $subscriber_id)); + } + + /** + * List subscribers for a form + * + * @param integer $form_id Form ID. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. + * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-form + * + * @return false|mixed + */ + public function get_form_subscriptions( + int $form_id, + string $subscriber_state = 'active', + \DateTime $created_after = null, + \DateTime $created_before = null, + \DateTime $added_after = null, + \DateTime $added_before = null, + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Build parameters. + $options = []; + + if (!empty($subscriber_state)) { + $options['status'] = $subscriber_state; + } + if (!is_null($created_after)) { + $options['created_after'] = $created_after->format('Y-m-d'); + } + if (!is_null($created_before)) { + $options['created_before'] = $created_before->format('Y-m-d'); + } + if (!is_null($added_after)) { + $options['added_after'] = $added_after->format('Y-m-d'); + } + if (!is_null($added_before)) { + $options['added_before'] = $added_before->format('Y-m-d'); + } + + // Send request. + return $this->get( + sprintf('forms/%s/subscribers', $form_id), + $this->build_total_count_and_pagination_params( + $options, + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Gets sequences + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @see https://developers.convertkit.com/v4.html#list-sequences + * + * @return false|mixed + */ + public function get_sequences( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + return $this->get( + 'sequences', + $this->build_total_count_and_pagination_params( + [], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Adds a subscriber to a sequence by email address + * + * @param integer $sequence_id Sequence ID. + * @param string $email_address Email Address. + * + * @see https://developers.convertkit.com/v4.html#add-subscriber-to-sequence-by-email-address + * + * @return false|mixed + */ + public function add_subscriber_to_sequence_by_email(int $sequence_id, string $email_address) + { + return $this->post( + sprintf('sequences/%s/subscribers', $sequence_id), + ['email_address' => $email_address] + ); + } + + /** + * Adds a subscriber to a sequence by subscriber ID + * + * @param integer $sequence_id Sequence ID. + * @param integer $subscriber_id Subscriber ID. + * + * @see https://developers.convertkit.com/v4.html#add-subscriber-to-sequence + * + * @since 2.0.0 + * + * @return false|mixed + */ + public function add_subscriber_to_sequence(int $sequence_id, int $subscriber_id) + { + return $this->post(sprintf('sequences/%s/subscribers/%s', $sequence_id, $subscriber_id)); + } + + /** + * List subscribers for a sequence + * + * @param integer $sequence_id Sequence ID. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $added_after Filter subscribers who have been added to the form after this date. + * @param \DateTime $added_before Filter subscribers who have been added to the form before this date. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-sequence + * + * @return false|mixed + */ + public function get_sequence_subscriptions( + int $sequence_id, + string $subscriber_state = 'active', + \DateTime $created_after = null, + \DateTime $created_before = null, + \DateTime $added_after = null, + \DateTime $added_before = null, + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Build parameters. + $options = []; + + if (!empty($subscriber_state)) { + $options['status'] = $subscriber_state; + } + if (!is_null($created_after)) { + $options['created_after'] = $created_after->format('Y-m-d'); + } + if (!is_null($created_before)) { + $options['created_before'] = $created_before->format('Y-m-d'); + } + if (!is_null($added_after)) { + $options['added_after'] = $added_after->format('Y-m-d'); + } + if (!is_null($added_before)) { + $options['added_before'] = $added_before->format('Y-m-d'); + } + + // Send request. + return $this->get( + sprintf('sequences/%s/subscribers', $sequence_id), + $this->build_total_count_and_pagination_params( + $options, + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * List tags. + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @see https://developers.convertkit.com/v4.html#list-tags + * + * @return false|array + */ + public function get_tags( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + return $this->get( + 'tags', + $this->build_total_count_and_pagination_params( + [], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Creates a tag. + * + * @param string $tag Tag Name. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#create-a-tag + * + * @return false|mixed + */ + public function create_tag(string $tag) + { + return $this->post( + 'tags', + ['name' => $tag] + ); + } + + /** + * Creates multiple tags. + * + * @param array $tags Tag Names. + * @param string $callback_url URL to notify for large batch size when async processing complete. + * + * @since 1.1.0 + * + * @see https://developers.convertkit.com/v4.html#bulk-create-tags + * + * @return false|mixed + */ + public function create_tags(array $tags, string $callback_url = '') + { + // Build parameters. + $options = [ + 'tags' => [], + ]; + foreach ($tags as $i => $tag) { + $options['tags'][] = [ + 'name' => (string) $tag, + ]; + } + + if (!empty($callback_url)) { + $options['callback_url'] = $callback_url; + } + + // Send request. + return $this->post( + 'bulk/tags', + $options + ); + } + + /** + * Tags a subscriber with the given existing Tag. + * + * @param integer $tag_id Tag ID. + * @param string $email_address Email Address. + * + * @see https://developers.convertkit.com/v4.html#tag-a-subscriber-by-email-address + * + * @return false|mixed + */ + public function tag_subscriber_by_email(int $tag_id, string $email_address) + { + return $this->post( + sprintf('tags/%s/subscribers', $tag_id), + ['email_address' => $email_address] + ); + } + + /** + * Tags a subscriber by subscriber ID with the given existing Tag. + * + * @param integer $tag_id Tag ID. + * @param integer $subscriber_id Subscriber ID. + * + * @see https://developers.convertkit.com/v4.html#tag-a-subscriber + * + * @return false|mixed + */ + public function tag_subscriber(int $tag_id, int $subscriber_id) + { + return $this->post(sprintf('tags/%s/subscribers/%s', $tag_id, $subscriber_id)); + } + + /** + * Removes a tag from a subscriber. + * + * @param integer $tag_id Tag ID. + * @param integer $subscriber_id Subscriber ID. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#remove-tag-from-subscriber + * + * @return false|mixed + */ + public function remove_tag_from_subscriber(int $tag_id, int $subscriber_id) + { + return $this->delete(sprintf('tags/%s/subscribers/%s', $tag_id, $subscriber_id)); + } + + /** + * Removes a tag from a subscriber by email address. + * + * @param integer $tag_id Tag ID. + * @param string $email_address Subscriber email address. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#remove-tag-from-subscriber-by-email-address + * + * @return false|mixed + */ + public function remove_tag_from_subscriber_by_email(int $tag_id, string $email_address) + { + return $this->delete( + sprintf('tags/%s/subscribers', $tag_id), + ['email_address' => $email_address] + ); + } + + /** + * List subscribers for a tag + * + * @param integer $tag_id Tag ID. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $tagged_after Filter subscribers who have been tagged after this date. + * @param \DateTime $tagged_before Filter subscribers who have been tagged before this date. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @see https://developers.convertkit.com/v4.html#list-subscribers-for-a-tag + * + * @return false|mixed + */ + public function get_tag_subscriptions( + int $tag_id, + string $subscriber_state = 'active', + \DateTime $created_after = null, + \DateTime $created_before = null, + \DateTime $tagged_after = null, + \DateTime $tagged_before = null, + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Build parameters. + $options = []; + + if (!empty($subscriber_state)) { + $options['status'] = $subscriber_state; + } + if (!is_null($created_after)) { + $options['created_after'] = $created_after->format('Y-m-d'); + } + if (!is_null($created_before)) { + $options['created_before'] = $created_before->format('Y-m-d'); + } + if (!is_null($tagged_after)) { + $options['tagged_after'] = $tagged_after->format('Y-m-d'); + } + if (!is_null($tagged_before)) { + $options['tagged_before'] = $tagged_before->format('Y-m-d'); + } + + // Send request. + return $this->get( + sprintf('tags/%s/subscribers', $tag_id), + $this->build_total_count_and_pagination_params( + $options, + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * List email templates. + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 2.0.0 + * + * @see https://developers.convertkit.com/v4.html#convertkit-api-email-templates + * + * @return false|mixed + */ + public function get_email_templates( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Send request. + return $this->get( + 'email_templates', + $this->build_total_count_and_pagination_params( + [], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * List subscribers. + * + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param string $email_address Search susbcribers by email address. This is an exact match search. + * @param \DateTime $created_after Filter subscribers who have been created after this date. + * @param \DateTime $created_before Filter subscribers who have been created before this date. + * @param \DateTime $updated_after Filter subscribers who have been updated after this date. + * @param \DateTime $updated_before Filter subscribers who have been updated before this date. + * @param string $sort_field Sort Field (id|updated_at|cancelled_at). + * @param string $sort_order Sort Order (asc|desc). + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 2.0.0 + * + * @see https://developers.convertkit.com/v4.html#list-subscribers + * + * @return false|mixed + */ + public function get_subscribers( + string $subscriber_state = 'active', + string $email_address = '', + \DateTime $created_after = null, + \DateTime $created_before = null, + \DateTime $updated_after = null, + \DateTime $updated_before = null, + string $sort_field = 'id', + string $sort_order = 'desc', + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Build parameters. + $options = []; + + if (!empty($subscriber_state)) { + $options['status'] = $subscriber_state; + } + if (!empty($email_address)) { + $options['email_address'] = $email_address; + } + if (!is_null($created_after)) { + $options['created_after'] = $created_after->format('Y-m-d'); + } + if (!is_null($created_before)) { + $options['created_before'] = $created_before->format('Y-m-d'); + } + if (!is_null($updated_after)) { + $options['updated_after'] = $updated_after->format('Y-m-d'); + } + if (!is_null($updated_before)) { + $options['updated_before'] = $updated_before->format('Y-m-d'); + } + if (!empty($sort_field)) { + $options['sort_field'] = $sort_field; + } + if (!empty($sort_order)) { + $options['sort_order'] = $sort_order; + } + + // Send request. + return $this->get( + 'subscribers', + $this->build_total_count_and_pagination_params( + $options, + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Create a subscriber. + * + * Behaves as an upsert. If a subscriber with the provided email address does not exist, + * it creates one with the specified first name and state. If a subscriber with the provided + * email address already exists, it updates the first name. + * + * @param string $email_address Email Address. + * @param string $first_name First Name. + * @param string $subscriber_state Subscriber State (active|bounced|cancelled|complained|inactive). + * @param array $fields Custom Fields. + * + * @since 2.0.0 + * + * @see https://developers.convertkit.com/v4.html#create-a-subscriber + * + * @return mixed + */ + public function create_subscriber( + string $email_address, + string $first_name = '', + string $subscriber_state = '', + array $fields = [] + ) { + // Build parameters. + $options = ['email_address' => $email_address]; + + if (!empty($first_name)) { + $options['first_name'] = $first_name; + } + if (!empty($subscriber_state)) { + $options['state'] = $subscriber_state; + } + if (count($fields)) { + $options['fields'] = $fields; + } + + // Send request. + return $this->post( + 'subscribers', + $options + ); + } + + /** + * Create multiple subscribers. + * + * @param array> $subscribers Subscribers. + * @param string $callback_url URL to notify for large batch size when async processing complete. + * + * @since 2.0.0 + * + * @see https://developers.convertkit.com/v4.html#bulk-create-subscribers + * + * @return mixed + */ + public function create_subscribers(array $subscribers, string $callback_url = '') + { + // Build parameters. + $options = ['subscribers' => $subscribers]; + + if (!empty($callback_url)) { + $options['callback_url'] = $callback_url; + } + + // Send request. + return $this->post( + 'bulk/subscribers', + $options + ); + } + + /** + * Get the ConvertKit subscriber ID associated with email address if it exists. + * Return false if subscriber not found. + * + * @param string $email_address Email Address. + * + * @throws \InvalidArgumentException If the email address is not a valid email format. + * + * @see https://developers.convertkit.com/v4.html#get-a-subscriber + * + * @return false|integer + */ + public function get_subscriber_id(string $email_address) + { + $subscribers = $this->get( + 'subscribers', + ['email_address' => $email_address] + ); + + // Return the subscriber's ID. + return $subscribers->subscribers[0]->id; + } + + /** + * Get subscriber by id + * + * @param integer $subscriber_id Subscriber ID. + * + * @see https://developers.convertkit.com/v4.html#get-a-subscriber + * + * @return false|integer + */ + public function get_subscriber(int $subscriber_id) + { + return $this->get(sprintf('subscribers/%s', $subscriber_id)); + } + + /** + * Updates the information for a single subscriber. + * + * @param integer $subscriber_id Existing Subscriber ID. + * @param string $first_name New First Name. + * @param string $email_address New Email Address. + * @param array $fields Updated Custom Fields. + * + * @see https://developers.convertkit.com/v4.html#update-a-subscriber + * + * @return false|mixed + */ + public function update_subscriber( + int $subscriber_id, + string $first_name = '', + string $email_address = '', + array $fields = [] + ) { + // Build parameters. + $options = []; + + if (!empty($first_name)) { + $options['first_name'] = $first_name; + } + if (!empty($email_address)) { + $options['email_address'] = $email_address; + } + if (!empty($fields)) { + $options['fields'] = $fields; + } + + // Send request. + return $this->put( + sprintf('subscribers/%s', $subscriber_id), + $options + ); + } + + /** + * Unsubscribe an email address. + * + * @param string $email_address Email Address. + * + * @see https://developers.convertkit.com/v4.html#unsubscribe-subscriber + * + * @return false|object + */ + public function unsubscribe_by_email(string $email_address) + { + return $this->post( + sprintf( + 'subscribers/%s/unsubscribe', + $this->get_subscriber_id($email_address) + ) + ); + } + + /** + * Unsubscribe the given subscriber ID. + * + * @param integer $subscriber_id Subscriber ID. + * + * @see https://developers.convertkit.com/v4.html#unsubscribe-subscriber + * + * @return false|object + */ + public function unsubscribe(int $subscriber_id) + { + return $this->post(sprintf('subscribers/%s/unsubscribe', $subscriber_id)); + } + + /** + * Get a list of the tags for a subscriber. + * + * @param integer $subscriber_id Subscriber ID. + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @see https://developers.convertkit.com/v4.html#list-tags-for-a-subscriber + * + * @return false|array + */ + public function get_subscriber_tags( + int $subscriber_id, + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + return $this->get( + sprintf('subscribers/%s/tags', $subscriber_id), + $this->build_total_count_and_pagination_params( + [], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * List broadcasts. + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @see https://developers.convertkit.com/v4.html#list-broadcasts + * + * @return false|mixed + */ + public function get_broadcasts( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Send request. + return $this->get( + 'broadcasts', + $this->build_total_count_and_pagination_params( + [], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Creates a broadcast. + * + * @param string $subject The broadcast email's subject. + * @param string $content The broadcast's email HTML content. + * @param string $description An internal description of this broadcast. + * @param boolean $public Specifies whether or not this is a public post. + * @param \DateTime $published_at Specifies the time that this post was published (applicable + * only to public posts). + * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create + * a draft broadcast. If set to a future time, this is the time that + * the broadcast will be scheduled to send. + * @param string $email_address Sending email address; leave blank to use your account's + * default sending email address. + * @param string $email_template_id ID of the email template to use; leave blank to use your + * account's default email template. + * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image + * (applicable only to public posts). + * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast + * post (applicable only to public posts). + * @param string $preview_text Specify the preview text of the email. + * @param array $subscriber_filter Filter subscriber(s) to send the email to. + * + * @see https://developers.convertkit.com/v4.html#create-a-broadcast + * + * @return false|object + */ + public function create_broadcast( + string $subject = '', + string $content = '', + string $description = '', + bool $public = false, + \DateTime $published_at = null, + \DateTime $send_at = null, + string $email_address = '', + string $email_template_id = '', + string $thumbnail_alt = '', + string $thumbnail_url = '', + string $preview_text = '', + array $subscriber_filter = [] + ) { + $options = [ + 'email_template_id' => $email_template_id, + 'email_address' => $email_address, + 'content' => $content, + 'description' => $description, + 'public' => $public, + 'published_at' => (!is_null($published_at) ? $published_at->format('Y-m-d H:i:s') : ''), + 'send_at' => (!is_null($send_at) ? $send_at->format('Y-m-d H:i:s') : ''), + 'thumbnail_alt' => $thumbnail_alt, + 'thumbnail_url' => $thumbnail_url, + 'preview_text' => $preview_text, + 'subject' => $subject, + ]; + if (count($subscriber_filter)) { + $options['subscriber_filter'] = $subscriber_filter; + } + + // Iterate through options, removing blank entries. + foreach ($options as $key => $value) { + if (is_string($value) && strlen($value) === 0) { + unset($options[$key]); + } + } + + // If the post isn't public, remove some options that don't apply. + if (!$public) { + unset($options['published_at'], $options['thumbnail_alt'], $options['thumbnail_url']); + } + + // Send request. + return $this->post( + 'broadcasts', + $options + ); + } + + /** + * Retrieve a specific broadcast. + * + * @param integer $id Broadcast ID. + * + * @see https://developers.convertkit.com/v4.html#get-a-broadcast + * + * @return false|object + */ + public function get_broadcast(int $id) + { + return $this->get(sprintf('broadcasts/%s', $id)); + } + + /** + * Get the statistics (recipient count, open rate, click rate, unsubscribe count, + * total clicks, status, and send progress) for a specific broadcast. + * + * @param integer $id Broadcast ID. + * + * @see https://developers.convertkit.com/v4.html#get-stats + * + * @return false|object + */ + public function get_broadcast_stats(int $id) + { + return $this->get(sprintf('broadcasts/%s/stats', $id)); + } + + /** + * Updates a broadcast. + * + * @param integer $id Broadcast ID. + * @param string $subject The broadcast email's subject. + * @param string $content The broadcast's email HTML content. + * @param string $description An internal description of this broadcast. + * @param boolean $public Specifies whether or not this is a public post. + * @param \DateTime $published_at Specifies the time that this post was published (applicable + * only to public posts). + * @param \DateTime $send_at Time that this broadcast should be sent; leave blank to create + * a draft broadcast. If set to a future time, this is the time that + * the broadcast will be scheduled to send. + * @param string $email_address Sending email address; leave blank to use your account's + * default sending email address. + * @param string $email_template_id ID of the email template to use; leave blank to use your + * account's default email template. + * @param string $thumbnail_alt Specify the ALT attribute of the public thumbnail image + * (applicable only to public posts). + * @param string $thumbnail_url Specify the URL of the thumbnail image to accompany the broadcast + * post (applicable only to public posts). + * @param string $preview_text Specify the preview text of the email. + * @param array $subscriber_filter Filter subscriber(s) to send the email to. + * + * @see https://developers.convertkit.com/#create-a-broadcast + * + * @return false|object + */ + public function update_broadcast( + int $id, + string $subject = '', + string $content = '', + string $description = '', + bool $public = false, + \DateTime $published_at = null, + \DateTime $send_at = null, + string $email_address = '', + string $email_template_id = '', + string $thumbnail_alt = '', + string $thumbnail_url = '', + string $preview_text = '', + array $subscriber_filter = [] + ) { + $options = [ + 'email_template_id' => $email_template_id, + 'email_address' => $email_address, + 'content' => $content, + 'description' => $description, + 'public' => $public, + 'published_at' => (!is_null($published_at) ? $published_at->format('Y-m-d H:i:s') : ''), + 'send_at' => (!is_null($send_at) ? $send_at->format('Y-m-d H:i:s') : ''), + 'thumbnail_alt' => $thumbnail_alt, + 'thumbnail_url' => $thumbnail_url, + 'preview_text' => $preview_text, + 'subject' => $subject, + ]; + if (count($subscriber_filter)) { + $options['subscriber_filter'] = $subscriber_filter; + } + + // Iterate through options, removing blank entries. + foreach ($options as $key => $value) { + if (is_string($value) && strlen($value) === 0) { + unset($options[$key]); + } + } + + // If the post isn't public, remove some options that don't apply. + if (!$public) { + unset($options['published_at'], $options['thumbnail_alt'], $options['thumbnail_url']); + } + + // Send request. + return $this->put( + sprintf('broadcasts/%s', $id), + $options + ); + } + + /** + * Deletes an existing broadcast. + * + * @param integer $id Broadcast ID. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#delete-a-broadcast + * + * @return false|object + */ + public function delete_broadcast(int $id) + { + return $this->delete(sprintf('broadcasts/%s', $id)); + } + + /** + * List webhooks. + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 2.0.0 + * + * @see https://developers.convertkit.com/v4.html#list-webhooks + * + * @return false|mixed + */ + public function get_webhooks( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Send request. + return $this->get( + 'webhooks', + $this->build_total_count_and_pagination_params( + [], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Creates a webhook that will be called based on the chosen event types. + * + * @param string $url URL to receive event. + * @param string $event Event to subscribe to. + * @param string $parameter Optional parameter depending on the event. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#create-a-webhook + * + * @throws \InvalidArgumentException If the event is not supported. + * + * @return false|object + */ + public function create_webhook(string $url, string $event, string $parameter = '') + { + // Depending on the event, build the required event array structure. + switch ($event) { + case 'subscriber.subscriber_activate': + case 'subscriber.subscriber_unsubscribe': + case 'subscriber.subscriber_bounce': + case 'subscriber.subscriber_complain': + case 'purchase.purchase_create': + $eventData = ['name' => $event]; + break; + + case 'subscriber.form_subscribe': + $eventData = [ + 'name' => $event, + 'form_id' => $parameter, + ]; + break; + + case 'subscriber.course_subscribe': + case 'subscriber.course_complete': + $eventData = [ + 'name' => $event, + 'course_id' => $parameter, + ]; + break; + + case 'subscriber.link_click': + $eventData = [ + 'name' => $event, + 'initiator_value' => $parameter, + ]; + break; + + case 'subscriber.product_purchase': + $eventData = [ + 'name' => $event, + 'product_id' => $parameter, + ]; + break; + + case 'subscriber.tag_add': + case 'subscriber.tag_remove': + $eventData = [ + 'name' => $event, + 'tag_id' => $parameter, + ]; + break; + + default: + throw new \InvalidArgumentException(sprintf('The event %s is not supported', $event)); + }//end switch + + // Send request. + return $this->post( + 'webhooks', + [ + 'target_url' => $url, + 'event' => $eventData, + ] + ); + } + + /** + * Deletes an existing webhook. + * + * @param integer $id Webhook ID. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#delete-a-webhook + * + * @return false|object + */ + public function delete_webhook(int $id) + { + return $this->delete(sprintf('webhooks/%s', $id)); + } + + /** + * List custom fields. + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#list-custom-fields + * + * @return false|mixed + */ + public function get_custom_fields( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Send request. + return $this->get( + 'custom_fields', + $this->build_total_count_and_pagination_params( + [], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Creates a custom field. + * + * @param string $label Custom Field label. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#create-a-custom-field + * + * @return false|object + */ + public function create_custom_field(string $label) + { + return $this->post( + 'custom_fields', + ['label' => $label] + ); + } + + /** + * Creates multiple custom fields. + * + * @param array $labels Custom Fields labels. + * @param string $callback_url URL to notify for large batch size when async processing complete. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#bulk-create-custom-fields + * + * @return false|object + */ + public function create_custom_fields(array $labels, string $callback_url = '') + { + // Build parameters. + $options = [ + 'custom_fields' => [], + ]; + foreach ($labels as $i => $label) { + $options['custom_fields'][] = [ + 'label' => (string) $label, + ]; + } + + if (!empty($callback_url)) { + $options['callback_url'] = $callback_url; + } + + // Send request. + return $this->post( + 'bulk/custom_fields', + $options + ); + } + + /** + * Updates an existing custom field. + * + * @param integer $id Custom Field ID. + * @param string $label Updated Custom Field label. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#update-a-custom-field + * + * @return false|object + */ + public function update_custom_field(int $id, string $label) + { + return $this->put( + sprintf('custom_fields/%s', $id), + ['label' => $label] + ); + } + + /** + * Deletes an existing custom field. + * + * @param integer $id Custom Field ID. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/#destroy-field + * + * @return false|object + */ + public function delete_custom_field(int $id) + { + return $this->delete(sprintf('custom_fields/%s', $id)); + } + + /** + * List purchases. + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 1.0.0 + * + * @see https://developers.convertkit.com/v4.html#list-purchases + * + * @return false|mixed + */ + public function get_purchases( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Send request. + return $this->get( + 'purchases', + $this->build_total_count_and_pagination_params( + [], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Retuns a specific purchase. + * + * @param integer $purchase_id Purchase ID. + * + * @see https://developers.convertkit.com/v4.html#get-a-purchase + * + * @return false|object + */ + public function get_purchase(int $purchase_id) + { + return $this->get(sprintf('purchases/%s', $purchase_id)); + } + + /** + * Creates a purchase. + * + * @param string $email_address Email Address. + * @param string $transaction_id Transaction ID. + * @param array $products Products. + * @param string $currency ISO Currency Code. + * @param string $first_name First Name. + * @param string $status Order Status. + * @param float $subtotal Subtotal. + * @param float $tax Tax. + * @param float $shipping Shipping. + * @param float $discount Discount. + * @param float $total Total. + * @param \DateTime $transaction_time Transaction date and time. + * + * @see https://developers.convertkit.com/v4.html#create-a-purchase + * + * @return false|object + */ + public function create_purchase( + string $email_address, + string $transaction_id, + array $products, + string $currency = 'USD', + string $first_name = null, + string $status = null, + float $subtotal = 0, + float $tax = 0, + float $shipping = 0, + float $discount = 0, + float $total = 0, + \DateTime $transaction_time = null + ) { + // Build parameters. + $options = [ + // Required fields. + 'email_address' => $email_address, + 'transaction_id' => $transaction_id, + 'products' => $products, + 'currency' => $currency, // Required, but if not provided, API will default to USD. + + // Optional fields. + 'first_name' => $first_name, + 'status' => $status, + 'subtotal' => $subtotal, + 'tax' => $tax, + 'shipping' => $shipping, + 'discount' => $discount, + 'total' => $total, + 'transaction_time' => (!is_null($transaction_time) ? $transaction_time->format('Y-m-d H:i:s') : ''), + ]; + + // Iterate through options, removing blank and null entries. + foreach ($options as $key => $value) { + if (is_null($value)) { + unset($options[$key]); + continue; + } + + if (is_string($value) && strlen($value) === 0) { + unset($options[$key]); + } + } + + return $this->post('purchases', $options); + } + + /** + * List segments. + * + * @param boolean $include_total_count To include the total count of records in the response, use true. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 2.0.0 + * + * @see https://developers.convertkit.com/v4.html#convertkit-api-segments + * + * @return false|mixed + */ + public function get_segments( + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + // Send request. + return $this->get( + 'segments', + $this->build_total_count_and_pagination_params( + [], + $include_total_count, + $after_cursor, + $before_cursor, + $per_page + ) + ); + } + + /** + * Converts any relative URls to absolute, fully qualified HTTP(s) URLs for the given + * DOM Elements. + * + * @param \DOMNodeList<\DOMElement> $elements Elements. + * @param string $attribute HTML Attribute. + * @param string $url Absolute URL to prepend to relative URLs. + * + * @since 1.0.0 + * + * @return void + */ + public function convert_relative_to_absolute_urls(\DOMNodeList $elements, string $attribute, string $url) // phpcs:ignore Squiz.Commenting.FunctionComment.IncorrectTypeHint, Generic.Files.LineLength.TooLong + { + // Anchor hrefs. + foreach ($elements as $element) { + // Skip if the attribute's value is empty. + if (empty($element->getAttribute($attribute))) { + continue; + } + + // Skip if the attribute's value is a fully qualified URL. + if (filter_var($element->getAttribute($attribute), FILTER_VALIDATE_URL)) { + continue; + } + + // Skip if this is a Google Font CSS URL. + if (strpos($element->getAttribute($attribute), '//fonts.googleapis.com') !== false) { + continue; + } + + // If here, the attribute's value is a relative URL, missing the http(s) and domain. + // Prepend the URL to the attribute's value. + $element->setAttribute($attribute, $url . $element->getAttribute($attribute)); + } + } + + /** + * Strips , and opening and closing tags from the given markup, + * as well as the Content-Type meta tag we might have added in get_html(). + * + * @param string $markup HTML Markup. + * + * @since 1.0.0 + * + * @return string HTML Markup + */ + public function strip_html_head_body_tags(string $markup) + { + $markup = str_replace('', '', $markup); + $markup = str_replace('', '', $markup); + $markup = str_replace('', '', $markup); + $markup = str_replace('', '', $markup); + $markup = str_replace('', '', $markup); + $markup = str_replace('', '', $markup); + $markup = str_replace('', '', $markup); + + return $markup; + } + + /** + * Adds total count and pagination parameters to the given array of existing API parameters. + * + * @param array $params API parameters. + * @param boolean $include_total_count Return total count of records. + * @param string $after_cursor Return results after the given pagination cursor. + * @param string $before_cursor Return results before the given pagination cursor. + * @param integer $per_page Number of results to return. + * + * @since 2.0.0 + * + * @return array + */ + private function build_total_count_and_pagination_params( + array $params = [], + bool $include_total_count = false, + string $after_cursor = '', + string $before_cursor = '', + int $per_page = 100 + ) { + $params['include_total_count'] = $include_total_count; + if (!empty($after_cursor)) { + $params['after'] = $after_cursor; + } + if (!empty($before_cursor)) { + $params['before'] = $before_cursor; + } + if (!empty($per_page)) { + $params['per_page'] = $per_page; + } + + return $params; + } + + /** + * Performs a GET request to the API. + * + * @param string $endpoint API Endpoint. + * @param array|string> $args Request arguments. + * + * @return false|mixed + */ + public function get(string $endpoint, array $args = []) + { + return $this->request($endpoint, 'GET', $args); + } + + /** + * Performs a POST request to the API. + * + * @param string $endpoint API Endpoint. + * @param array>> $args Request arguments. + * + * @return false|mixed + */ + public function post(string $endpoint, array $args = []) + { + return $this->request($endpoint, 'POST', $args); + } + + /** + * Performs a PUT request to the API. + * + * @param string $endpoint API Endpoint. + * @param array|string> $args Request arguments. + * + * @return false|mixed + */ + public function put(string $endpoint, array $args = []) + { + return $this->request($endpoint, 'PUT', $args); + } + + /** + * Performs a DELETE request to the API. + * + * @param string $endpoint API Endpoint. + * @param array|string> $args Request arguments. + * + * @return false|mixed + */ + public function delete(string $endpoint, array $args = []) + { + return $this->request($endpoint, 'DELETE', $args); + } + + /** + * Performs an API request. + * + * @param string $endpoint API Endpoint. + * @param string $method Request method. + * @param array>> $args Request arguments. + * + * @throws \Exception If JSON encoding arguments failed. + * + * @return false|mixed + */ + abstract public function request(string $endpoint, string $method, array $args = []); + + /** + * Returns the headers to use in an API request. + * + * @param string $type Accept and Content-Type Headers. + * @param boolean $auth Include authorization header. + * + * @since 2.0.0 + * + * @return array + */ + abstract public function get_request_headers(string $type = 'application/json', bool $auth = true); + + /** + * Returns the maximum amount of time to wait for + * a response to the request before exiting. + * + * @since 2.0.0 + * + * @return int Timeout, in seconds. + */ + abstract public function get_timeout(); + + /** + * Returns the user agent string to use in all HTTP requests. + * + * @since 2.0.0 + * + * @return string + */ + abstract public function get_user_agent(); +} From d93c3965446ced1c3adf8e18feea800995b50e7f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 12 Apr 2024 18:38:30 +0100 Subject: [PATCH 75/78] Coding Standards --- src/ConvertKit_API.php | 11 +++++------ src/ConvertKit_API_Traits.php | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index d77bd42..092a5c8 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -19,7 +19,7 @@ class ConvertKit_API { use ConvertKit_API_Traits; - + /** * Debug * @@ -427,16 +427,15 @@ public function get_request_headers(string $type = 'application/json', bool $aut * Returns the maximum amount of time to wait for * a response to the request before exiting. * - * @since 2.0.0 + * @since 2.0.0 * - * @return int Timeout, in seconds. + * @return integer Timeout, in seconds. */ - public function get_timeout() { - + public function get_timeout() + { $timeout = 10; return $timeout; - } /** diff --git a/src/ConvertKit_API_Traits.php b/src/ConvertKit_API_Traits.php index d71f644..1c8c63b 100644 --- a/src/ConvertKit_API_Traits.php +++ b/src/ConvertKit_API_Traits.php @@ -1796,9 +1796,9 @@ abstract public function get_request_headers(string $type = 'application/json', * Returns the maximum amount of time to wait for * a response to the request before exiting. * - * @since 2.0.0 + * @since 2.0.0 * - * @return int Timeout, in seconds. + * @return integer Timeout, in seconds. */ abstract public function get_timeout(); From e73f4927c6c84c9213c981be590d4123f42d5e90 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 12 Apr 2024 23:30:28 +0100 Subject: [PATCH 76/78] Move `VERSION` const --- src/ConvertKit_API.php | 7 +++++++ src/ConvertKit_API_Traits.php | 7 ------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 092a5c8..86b1888 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -20,6 +20,13 @@ class ConvertKit_API { use ConvertKit_API_Traits; + /** + * The SDK version. + * + * @var string + */ + public const VERSION = '2.0.0'; + /** * Debug * diff --git a/src/ConvertKit_API_Traits.php b/src/ConvertKit_API_Traits.php index 1c8c63b..690fdb8 100644 --- a/src/ConvertKit_API_Traits.php +++ b/src/ConvertKit_API_Traits.php @@ -12,13 +12,6 @@ */ trait ConvertKit_API_Traits { - /** - * The SDK version. - * - * @var string - */ - public const VERSION = '2.0.0'; - /** * ConvertKit OAuth Application Client ID * From 6277e112ccfeea41d783d5616e50984c3d797c54 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 12 Apr 2024 23:46:59 +0100 Subject: [PATCH 77/78] Fix tests to call correct `get_request_headers` method --- tests/ConvertKitAPITest.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 2d95c73..b6839a3 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -327,7 +327,7 @@ public function testDebugDisabled() */ public function testRequestHeadersMethod() { - $headers = $this->callPrivateMethod($this->api, 'request_headers', []); + $headers = $this->api->get_request_headers(); $this->assertArrayHasKey('Accept', $headers); $this->assertArrayHasKey('Content-Type', $headers); $this->assertArrayHasKey('User-Agent', $headers); @@ -348,9 +348,9 @@ public function testRequestHeadersMethod() */ public function testRequestHeadersMethodWithType() { - $headers = $this->callPrivateMethod($this->api, 'request_headers', [ - 'type' => 'text/html', - ]); + $headers = $this->api->get_request_headers( + type: 'text/html' + ); $this->assertArrayHasKey('Accept', $headers); $this->assertArrayHasKey('Content-Type', $headers); $this->assertArrayHasKey('User-Agent', $headers); @@ -371,9 +371,9 @@ public function testRequestHeadersMethodWithType() */ public function testRequestHeadersMethodWithAuthDisabled() { - $headers = $this->callPrivateMethod($this->api, 'request_headers', [ - 'auth' => false, - ]); + $headers = $this->api->get_request_headers( + auth: false + ); $this->assertArrayHasKey('Accept', $headers); $this->assertArrayHasKey('Content-Type', $headers); $this->assertArrayHasKey('User-Agent', $headers); @@ -393,10 +393,10 @@ public function testRequestHeadersMethodWithAuthDisabled() */ public function testRequestHeadersMethodWithTypeAndAuthDisabled() { - $headers = $this->callPrivateMethod($this->api, 'request_headers', [ - 'type' => 'text/html', - 'auth' => false, - ]); + $headers = $this->api->get_request_headers( + type: 'text/html', + auth: false + ); $this->assertArrayHasKey('Accept', $headers); $this->assertArrayHasKey('Content-Type', $headers); $this->assertArrayHasKey('User-Agent', $headers); From 32764769207fe71263a8c5f1815d3e93c415bb7f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 12 Apr 2024 23:53:12 +0100 Subject: [PATCH 78/78] Fix `get_subscriber_id` definition when no subscribers found by email --- src/ConvertKit_API_Traits.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ConvertKit_API_Traits.php b/src/ConvertKit_API_Traits.php index 690fdb8..b5fc993 100644 --- a/src/ConvertKit_API_Traits.php +++ b/src/ConvertKit_API_Traits.php @@ -863,6 +863,10 @@ public function get_subscriber_id(string $email_address) ['email_address' => $email_address] ); + if (!count($subscribers->subscribers)) { + return false; + } + // Return the subscriber's ID. return $subscribers->subscribers[0]->id; }