diff --git a/src/ConvertKit_API.php b/src/ConvertKit_API.php index 3075af4..19a4928 100644 --- a/src/ConvertKit_API.php +++ b/src/ConvertKit_API.php @@ -430,82 +430,122 @@ public function get_form_subscriptions( } /** - * Gets all sequences + * Gets sequences * - * @see https://developers.convertkit.com/#list-sequences + * @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() + public function get_sequences(string $after_cursor = '', string $before_cursor = '', int $per_page = 100) { - return $this->get('sequences'); + return $this->get( + endpoint: 'sequences', + args: $this->build_pagination_params( + after_cursor: $after_cursor, + before_cursor: $before_cursor, + per_page: $per_page + ) + ); } /** * Adds a subscriber to a sequence by email address * - * @param integer $sequence_id Sequence ID. - * @param string $email Email Address. - * @param string $first_name First Name. - * @param array $fields Custom Fields. - * @param array $tag_ids Tag ID(s) to subscribe to. + * @param integer $sequence_id Sequence ID. + * @param string $email Email Address. * - * @see https://developers.convertkit.com/#add-subscriber-to-a-sequence + * @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, - string $first_name = '', - array $fields = [], - array $tag_ids = [] - ) { - // Build parameters. - $options = ['email' => $email]; - - if (!empty($first_name)) { - $options['first_name'] = $first_name; - } - if (!empty($fields)) { - $options['fields'] = $fields; - } - if (!empty($tag_ids)) { - $options['tags'] = $tag_ids; - } - - // Send request. + public function add_subscriber_to_sequence(int $sequence_id, string $email) + { return $this->post( - sprintf('sequences/%s/subscribe', $sequence_id), - $options + endpoint: sprintf('sequences/%s/subscribers', $sequence_id), + args: ['email_address' => $email] ); } /** - * Gets subscribers to a sequence + * Adds a subscriber to a sequence by subscriber ID * - * @param integer $sequence_id Sequence ID. - * @param string $sort_order Sort Order (asc|desc). - * @param string $subscriber_state Subscriber State (active,cancelled). - * @param integer $page Page. + * @param integer $sequence_id Sequence ID. + * @param integer $subscriber_id Subscriber ID. * - * @see https://developers.convertkit.com/#list-subscriptions-to-a-sequence + * @see https://developers.convertkit.com/v4.html#add-subscriber-to-sequence + * + * @since 2.0.0 + * + * @return false|mixed + */ + public function add_subscriber_to_sequence_by_subscriber_id(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 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 $sort_order = 'asc', string $subscriber_state = 'active', - int $page = 1 + \DateTime $created_after = null, + \DateTime $created_before = null, + \DateTime $added_after = null, + \DateTime $added_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('sequences/%s/subscriptions', $sequence_id), - [ - 'sort_order' => $sort_order, - 'subscriber_state' => $subscriber_state, - 'page' => $page, - ] + endpoint: sprintf('sequences/%s/subscribers', $sequence_id), + args: $options ); } diff --git a/tests/ConvertKitAPITest.php b/tests/ConvertKitAPITest.php index 9391fcd..eabfaab 100644 --- a/tests/ConvertKitAPITest.php +++ b/tests/ConvertKitAPITest.php @@ -639,13 +639,14 @@ public function testGetFormSubscriptionsWithInvalidFormID() */ public function testGetSequences() { - $this->markTestIncomplete(); - $result = $this->api->get_sequences(); - $this->assertInstanceOf('stdClass', $result); + + // Assert sequences and pagination exist. + $this->assertDataExists($result, 'sequences'); + $this->assertPaginationExists($result); // Check first sequence in resultset has expected data. - $sequence = get_object_vars($result->courses[0]); + $sequence = get_object_vars($result->sequences[0]); $this->assertArrayHasKey('id', $sequence); $this->assertArrayHasKey('name', $sequence); $this->assertArrayHasKey('hold', $sequence); @@ -653,6 +654,66 @@ public function testGetSequences() $this->assertArrayHasKey('created_at', $sequence); } + /** + * Test that get_sequences() returns the expected data when + * pagination parameters and per_page limits are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSequencesPagination() + { + $result = $this->api->get_sequences( + per_page: 1 + ); + + // Assert sequences and pagination exist. + $this->assertDataExists($result, 'sequences'); + $this->assertPaginationExists($result); + + // 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); + + // Use pagination to fetch next page. + $result = $this->api->get_sequences( + per_page: 1, + after_cursor: $result->pagination->end_cursor + ); + + // Assert sequences and pagination exist. + $this->assertDataExists($result, 'sequences'); + $this->assertPaginationExists($result); + + // Assert a single sequence was returned. + $this->assertCount(1, $result->sequences); + + // 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_sequences( + per_page: 1, + before_cursor: $result->pagination->start_cursor + ); + + // Assert sequences and pagination exist. + $this->assertDataExists($result, 'sequences'); + $this->assertPaginationExists($result); + + // 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); + } + /** * Test that add_subscriber_to_sequence() returns the expected data. * @@ -662,14 +723,17 @@ public function testGetSequences() */ public function testAddSubscriberToSequence() { - $this->markTestIncomplete(); - $result = $this->api->add_subscriber_to_sequence( sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_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'] + ); } /** @@ -682,12 +746,10 @@ public function testAddSubscriberToSequence() */ public function testAddSubscriberToSequenceWithInvalidSequenceID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $result = $this->api->add_subscriber_to_sequence( sequence_id: 12345, - email: $this->generateEmailAddress() + email: $_ENV['CONVERTKIT_API_SUBSCRIBER_EMAIL'] ); } @@ -701,8 +763,6 @@ public function testAddSubscriberToSequenceWithInvalidSequenceID() */ public function testAddSubscriberToSequenceWithInvalidEmailAddress() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $result = $this->api->add_subscriber_to_sequence( sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], @@ -711,189 +771,324 @@ public function testAddSubscriberToSequenceWithInvalidEmailAddress() } /** - * Test that add_subscriber_to_sequence() returns the expected data - * when a first_name parameter is included. + * Test that add_subscriber_to_sequence_by_subscriber_id() returns the expected data. * - * @since 1.0.0 + * @since 2.0.0 * * @return void */ - public function testAddSubscriberToSequenceWithFirstName() + public function testAddSubscriberToSequenceByID() { - $this->markTestIncomplete(); - - $emailAddress = $this->generateEmailAddress(); - $firstName = 'First Name'; - $result = $this->api->add_subscriber_to_sequence( - sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - email: $emailAddress, - first_name: $firstName + $result = $this->api->add_subscriber_to_sequence_by_subscriber_id( + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], + subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] ); - $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); + $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']); } /** - * Test that add_subscriber_to_sequence() returns the expected data - * when custom field data is included. + * Test that add_subscriber_to_sequence_by_subscriber_id() throws a ClientException when an invalid + * sequence ID is specified. * - * @since 1.0.0 + * @since 2.0.0 * * @return void */ - public function testAddSubscriberToSequenceWithCustomFields() + public function testAddSubscriberToSequenceByIDWithInvalidSequenceID() { - $this->markTestIncomplete(); + $this->expectException(ClientException::class); + $result = $this->api->add_subscriber_to_sequence_by_subscriber_id( + sequence_id: 12345, + subscriber_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'] + ); + } - $result = $this->api->add_subscriber_to_sequence( - sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - email: $this->generateEmailAddress(), - first_name: 'First Name', - fields: [ - 'last_name' => 'Last Name', - ] + /** + * Test that add_subscriber_to_sequence_by_subscriber_id() throws a ClientException when an invalid + * email address is specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testAddSubscriberToSequenceByIDWithInvalidSubscriberID() + { + $this->expectException(ClientException::class); + $result = $this->api->add_subscriber_to_sequence_by_subscriber_id( + sequence_id: $_ENV['CONVERTKIT_API_SUBSCRIBER_ID'], + subscriber_id: 12345 ); + } - // Check subscription object returned. - $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscription', get_object_vars($result)); + /** + * Test that get_sequence_subscriptions() returns the expected data. + * + * @since 1.0.0 + * + * @return void + */ + public function testGetSequenceSubscriptions() + { + $result = $this->api->get_sequence_subscriptions( + sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_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'); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); } /** - * Test that add_subscriber_to_sequence() returns the expected data - * when custom field data is included. + * Test that get_sequence_subscriptions() returns the expected data + * when a valid Sequence ID is specified and the subscription status + * is cancelled. * * @since 1.0.0 * * @return void */ - public function testAddSubscriberToSequenceWithTagID() + public function testGetSequenceSubscriptionsWithBouncedSubscriberState() { - $this->markTestIncomplete(); + $result = $this->api->get_sequence_subscriptions( + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], + subscriber_state: 'bounced' + ); - $result = $this->api->add_subscriber_to_sequence( - sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - email: $this->generateEmailAddress(), - first_name: 'First Name', - tag_ids: [ - (int) $_ENV['CONVERTKIT_API_TAG_ID'] - ] + // 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_sequence_subscriptions() returns the expected data + * when a valid Sequence ID is specified and the added_after parameter + * is used. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSequenceSubscriptionsWithAddedAfterParam() + { + $date = new \DateTime('2024-01-01'); + $result = $this->api->get_sequence_subscriptions( + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], + added_after: $date ); - // Check subscription object returned. - $this->assertInstanceOf('stdClass', $result); - $this->assertArrayHasKey('subscription', get_object_vars($result)); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); - // Fetch subscriber tags from API to confirm the tag saved. - $subscriberTags = $this->api->get_subscriber_tags($result->subscription->subscriber->id); - $this->assertEquals($subscriberTags->tags[0]->id, $_ENV['CONVERTKIT_API_TAG_ID']); + // 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_sequence_subscriptions() returns the expected data. + * Test that get_sequence_subscriptions() returns the expected data + * when a valid Sequence ID is specified and the added_before parameter + * is used. * - * @since 1.0.0 + * @since 2.0.0 * * @return void */ - public function testGetSequenceSubscriptions() + public function testGetSequenceSubscriptionsWithAddedBeforeParam() { - $this->markTestIncomplete(); + $date = new \DateTime('2024-01-01'); + $result = $this->api->get_sequence_subscriptions( + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], + added_before: $date + ); - $result = $this->api->get_sequence_subscriptions($_ENV['CONVERTKIT_API_SEQUENCE_ID']); - $this->assertInstanceOf('stdClass', $result); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); - // Assert expected keys exist. - $result = get_object_vars($result); - $this->assertArrayHasKey('total_subscriptions', $result); - $this->assertArrayHasKey('page', $result); - $this->assertArrayHasKey('total_pages', $result); - $this->assertArrayHasKey('subscriptions', $result); + // Check the correct subscribers were returned. + $this->assertLessThanOrEqual( + $date->format('Y-m-d'), + date('Y-m-d', strtotime($result->subscribers[0]->added_at)) + ); + } - // Assert subscriptions exist. - $this->assertIsArray($result['subscriptions']); + /** + * Test that get_sequence_subscriptions() returns the expected data + * when a valid Sequence ID is specified and the created_after parameter + * is used. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSequenceSubscriptionsWithCreatedAfterParam() + { + $date = new \DateTime('2024-01-01'); + $result = $this->api->get_sequence_subscriptions( + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], + created_after: $date + ); - // Assert sort order is ascending. + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); + + // 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]->created_at)) ); } /** - * Test that get_sequence_subscriptions() returns the expected data in descending order. + * Test that get_sequence_subscriptions() returns the expected data + * when a valid Sequence ID is specified and the created_before parameter + * is used. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSequenceSubscriptionsWithCreatedBeforeParam() + { + $date = new \DateTime('2024-01-01'); + $result = $this->api->get_sequence_subscriptions( + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], + created_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]->created_at)) + ); + } + + /** + * Test that get_sequence_subscriptions() returns the expected data + * when a valid Sequence ID is specified and pagination parameters + * and per_page limits are specified. * * @since 1.0.0 * * @return void */ - public function testGetSequenceSubscriptionsWithDescSortOrder() + public function testGetSequenceSubscriptionsPagination() { - $this->markTestIncomplete(); + $result = $this->api->get_sequence_subscriptions( + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_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_sequence_subscriptions( - sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - sort_order: 'desc' + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], + per_page: 1, + after_cursor: $result->pagination->end_cursor ); - $this->assertInstanceOf('stdClass', $result); - $result = get_object_vars($result); - $this->assertArrayHasKey('total_subscriptions', $result); - $this->assertArrayHasKey('page', $result); - $this->assertArrayHasKey('total_pages', $result); - $this->assertArrayHasKey('subscriptions', $result); + // Assert subscribers and pagination exist. + $this->assertDataExists($result, 'subscribers'); + $this->assertPaginationExists($result); - // Assert subscriptions exist. - $this->assertIsArray($result['subscriptions']); + // Assert a single subscriber was returned. + $this->assertCount(1, $result->subscribers); - // Assert sort order. - $this->assertLessThanOrEqual( - $result['subscriptions'][0]->created_at, - $result['subscriptions'][1]->created_at + // 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_sequence_subscriptions( + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_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); } /** * Test that get_sequence_subscriptions() throws a ClientException when an invalid - * sort order is specified. + * Sequence ID is specified. * * @since 1.0.0 * * @return void */ - public function testGetSequenceSubscriptionsWithInvalidSortOrder() + public function testGetSequenceSubscriptionsWithInvalidSequenceID() { - $this->markTestIncomplete(); - $this->expectException(ClientException::class); $result = $this->api->get_sequence_subscriptions( - sequence_id: $_ENV['CONVERTKIT_API_SEQUENCE_ID'], - sort_order: 'invalidSortOrder' + sequence_id: 12345 ); } /** * Test that get_sequence_subscriptions() throws a ClientException when an invalid - * sequence ID is specified. + * subscriber state is specified. * - * @since 1.0.0 + * @since 2.0.0 * * @return void */ - public function testGetSequenceSubscriptionsWithInvalidSequenceID() + public function testGetSequenceSubscriptionsWithInvalidSubscriberState() { $this->expectException(ClientException::class); - $result = $this->api->get_sequence_subscriptions(12345); + $result = $this->api->get_sequence_subscriptions( + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], + subscriber_state: 'not-a-valid-state' + ); + } + + /** + * Test that get_sequence_subscriptions() throws a ClientException when invalid + * pagination parameters are specified. + * + * @since 2.0.0 + * + * @return void + */ + public function testGetSequenceSubscriptionsWithInvalidPagination() + { + $this->expectException(ClientException::class); + $result = $this->api->get_sequence_subscriptions( + sequence_id: (int) $_ENV['CONVERTKIT_API_SEQUENCE_ID'], + after_cursor: 'not-a-valid-cursor' + ); } /**