diff --git a/.env.dist.testing b/.env.dist.testing index 0229b125..57006190 100644 --- a/.env.dist.testing +++ b/.env.dist.testing @@ -43,6 +43,7 @@ CONVERTKIT_API_PRODUCT_NAME="Newsletter Subscription" CONVERTKIT_API_PRODUCT_ID="36377" CONVERTKIT_API_PRODUCT_URL="https://cheerful-architect-3237.ck.page/products/newsletter-subscription" CONVERTKIT_API_PRODUCT_DISCOUNT_CODE=B7G96H637H +CONVERTKIT_API_SEQUENCE_NAME="WordPress Sequence" CONVERTKIT_API_SEQUENCE_ID="1030824" CONVERTKIT_API_TAG_NAME="wordpress" CONVERTKIT_API_TAG_ID="2744672" diff --git a/.env.example b/.env.example index 9d34bc3c..c396643c 100644 --- a/.env.example +++ b/.env.example @@ -49,6 +49,7 @@ CONVERTKIT_API_PRODUCT_NAME="Newsletter Subscription" CONVERTKIT_API_PRODUCT_ID="36377" CONVERTKIT_API_PRODUCT_URL="https://cheerful-architect-3237.ck.page/products/newsletter-subscription" CONVERTKIT_API_PRODUCT_DISCOUNT_CODE=B7G96H637H +CONVERTKIT_API_SEQUENCE_NAME="WordPress Sequence" CONVERTKIT_API_SEQUENCE_ID="1030824" CONVERTKIT_API_TAG_NAME="wordpress" CONVERTKIT_API_TAG_ID="2744672" diff --git a/admin/section/class-convertkit-settings-general.php b/admin/section/class-convertkit-settings-general.php index 101e3e70..59d6cea3 100644 --- a/admin/section/class-convertkit-settings-general.php +++ b/admin/section/class-convertkit-settings-general.php @@ -436,6 +436,9 @@ public function custom_post_types_callback( $args ) { $products = new ConvertKit_Resource_Products( 'settings' ); $products->refresh(); + $sequences = new ConvertKit_Resource_Sequences( 'settings' ); + $sequences->refresh(); + $tags = new ConvertKit_Resource_Tags( 'settings' ); $tags->refresh(); } diff --git a/includes/class-convertkit-resource-sequences.php b/includes/class-convertkit-resource-sequences.php new file mode 100644 index 00000000..fad515dd --- /dev/null +++ b/includes/class-convertkit-resource-sequences.php @@ -0,0 +1,58 @@ +has_access_and_refresh_token() ) { + $this->api = new ConvertKit_API_V4( + CONVERTKIT_OAUTH_CLIENT_ID, + CONVERTKIT_OAUTH_CLIENT_REDIRECT_URI, + $settings->get_access_token(), + $settings->get_refresh_token(), + $settings->debug_enabled(), + $context + ); + } + + // Call parent initialization function. + parent::init(); + + } + +} diff --git a/includes/class-convertkit-setup.php b/includes/class-convertkit-setup.php index 900c62a2..16200426 100644 --- a/includes/class-convertkit-setup.php +++ b/includes/class-convertkit-setup.php @@ -44,6 +44,13 @@ public function update() { return; } + /** + * 2.5.2: Migrate Contact Form 7 to ConvertKit Form Mappings + */ + if ( ! $current_version || version_compare( $current_version, '2.5.2', '<' ) ) { + $this->migrate_contact_form_7_form_mapping_settings(); + } + /** * 2.5.0: Get Access token for API version 4.0 using a v3 API Key and Secret. */ @@ -90,6 +97,51 @@ public function update() { } + /** + * 2.5.2: Prefix any Contact Form 7 to ConvertKit Form ID mappings with `form_`, now that + * the Plugin supports adding a subscriber to a Form, Tag or Sequence. + * + * @since 2.5.2 + */ + private function migrate_contact_form_7_form_mapping_settings() { + + $convertkit_contact_form_7_settings = new ConvertKit_ContactForm7_Settings(); + + // Bail if no settings exist. + if ( ! $convertkit_contact_form_7_settings->has_settings() ) { + return; + } + + // Get settings. + $settings = $convertkit_contact_form_7_settings->get(); + + // Iterate through settings. + foreach ( $settings as $contact_form_7_form_id => $convertkit_form_id ) { + // Skip keys that are non-numeric e.g. `creator_network_recommendations_*`. + if ( ! is_numeric( $contact_form_7_form_id ) ) { + continue; + } + + // Skip values that are blank i.e. no ConvertKit Form ID specified. + if ( empty( $convertkit_form_id ) ) { + continue; + } + + // Skip values that are non-numeric i.e. the `form_` prefix was already added. + // This should never happen as this routine runs once, but this is a sanity check. + if ( ! is_numeric( $convertkit_form_id ) ) { + continue; + } + + // Prefix the ConvertKit Form ID with `form_`. + $settings[ $contact_form_7_form_id ] = 'form:' . $convertkit_form_id; + } + + // Update settings. + update_option( $convertkit_contact_form_7_settings::SETTINGS_NAME, $settings ); + + } + /** * 2.5.0: Fetch an Access Token, Refresh Token and Expiry for v4 API use * based on the Plugin setting's v3 API Key and Secret. diff --git a/includes/functions.php b/includes/functions.php index 157e7647..23049f45 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -548,11 +548,14 @@ function convertkit_get_file_contents( $local_file ) { * @param string $value Field value. * @param string $id Field ID attribute. * @param string $css_class Field CSS class(es). + * @param string $context Resource context. */ -function convertkit_get_subscription_dropdown_field( $name, $value, $id, $css_class = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter +function convertkit_get_subscription_dropdown_field( $name, $value, $id, $css_class = '', $context = '' ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter // Load resource classes. - $forms = new ConvertKit_Resource_Forms( 'contact_form_7' ); + $forms = new ConvertKit_Resource_Forms( $context ); + $tags = new ConvertKit_Resource_Tags( $context ); + $sequences = new ConvertKit_Resource_Sequences( $context ); ob_start(); include CONVERTKIT_PLUGIN_PATH . '/views/backend/subscription-dropdown-field.php'; diff --git a/includes/integrations/contactform7/class-convertkit-contactform7-admin-settings.php b/includes/integrations/contactform7/class-convertkit-contactform7-admin-settings.php index 89e0cbe9..ad969f25 100644 --- a/includes/integrations/contactform7/class-convertkit-contactform7-admin-settings.php +++ b/includes/integrations/contactform7/class-convertkit-contactform7-admin-settings.php @@ -137,7 +137,8 @@ public function render() { '_wp_convertkit_integration_contactform7_settings[' . $cf7_form['id'] . ']', (string) $this->settings->get_convertkit_subscribe_setting_by_cf7_form_id( $cf7_form['id'] ), '_wp_convertkit_integration_contactform7_settings_' . $cf7_form['id'], - 'widefat' + 'widefat', + 'contact_form_7' ), 'email' => 'your-email', 'name' => 'your-name', diff --git a/includes/integrations/contactform7/class-convertkit-contactform7.php b/includes/integrations/contactform7/class-convertkit-contactform7.php index 6714fb34..7e1f7cb9 100644 --- a/includes/integrations/contactform7/class-convertkit-contactform7.php +++ b/includes/integrations/contactform7/class-convertkit-contactform7.php @@ -129,29 +129,54 @@ public function handle_wpcf7_submit( $contact_form, $result ) { 'contact_form_7' ); - // If the setting is 'Subscribe', just create the subscriber without assigning to a resource. - if ( $convertkit_subscribe_setting === 'subscribe' ) { - return $api->create_subscriber( - $email, - $first_name - ); + // Subscribe the email address. + $subscriber = $api->create_subscriber( $email, $first_name ); + if ( is_wp_error( $subscriber ) ) { + return; } - // For Legacy Forms, a different endpoint is used. - $forms = new ConvertKit_Resource_Forms(); - if ( $forms->is_legacy( $convertkit_subscribe_setting ) ) { - return $api->legacy_form_subscribe( - (int) $convertkit_subscribe_setting, - $email, - $first_name - ); + // If the setting is 'Subscribe', no Form needs to be assigned to the subscriber. + if ( $convertkit_subscribe_setting === 'subscribe' ) { + return; } - return $api->form_subscribe( - (int) $convertkit_subscribe_setting, - $email, - $first_name - ); + // Determine the resource type and ID to assign to the subscriber. + list( $resource_type, $resource_id ) = explode( ':', $convertkit_subscribe_setting ); + + // Cast ID. + $resource_id = absint( $resource_id ); + + // Add the subscriber to the resource type (form, tag etc). + switch ( $resource_type ) { + + /** + * Form + */ + case 'form': + // For Legacy Forms, a different endpoint is used. + $forms = new ConvertKit_Resource_Forms(); + if ( $forms->is_legacy( $resource_id ) ) { + return $api->add_subscriber_to_legacy_form( $resource_id, $subscriber['subscriber']['id'] ); + } + + // Add subscriber to form. + return $api->add_subscriber_to_form( $resource_id, $subscriber['subscriber']['id'] ); + + /** + * Sequence + */ + case 'sequence': + // Add subscriber to sequence. + return $api->add_subscriber_to_sequence( $resource_id, $subscriber['subscriber']['id'] ); + + /** + * Tag + */ + case 'tag': + // Add subscriber to tag. + return $api->tag_subscriber( $resource_id, $subscriber['subscriber']['id'] ); + + } } diff --git a/tests/_support/Helper/Acceptance/ConvertKitAPI.php b/tests/_support/Helper/Acceptance/ConvertKitAPI.php index b750b317..9b5d28d2 100644 --- a/tests/_support/Helper/Acceptance/ConvertKitAPI.php +++ b/tests/_support/Helper/Acceptance/ConvertKitAPI.php @@ -60,7 +60,41 @@ public function apiCheckSubscriberExists($I, $emailAddress) // Check at least one subscriber was returned and it matches the email address. $I->assertGreaterThan(0, $results['pagination']['total_count']); $I->assertEquals($emailAddress, $results['subscribers'][0]['email_address']); + + // Return subscriber ID. + return $results['subscribers'][0]['id']; + } + + /** + * Check the given subscriber ID has been assigned to the given sequence ID. + * + * @since 2.5.2 + * + * @param AcceptanceTester $I AcceptanceTester. + * @param int $subscriberID Subscriber ID. + * @param int $sequenceID Sequence ID. + */ + public function apiCheckSubscriberHasSequence($I, $subscriberID, $sequenceID) + { + // Run request. + $results = $this->apiRequest( + 'sequences/' . $sequenceID . '/subscribers', + 'GET' + ); + + // Iterate through subscribers. + $subscriberHasSequence = false; + foreach ($results['subscribers'] as $subscriber) { + if ($subscriber['id'] === $subscriberID) { + $subscriberHasSequence = true; + break; + } + } + + // Assert if the subscriber has the sequence. + $this->assertTrue($subscriberHasSequence); } + /** * Check the given subscriber ID has been assigned to the given tag ID. * diff --git a/tests/acceptance/integrations/other/ContactForm7FormCest.php b/tests/acceptance/integrations/other/ContactForm7FormCest.php index eab90590..21d1088c 100644 --- a/tests/acceptance/integrations/other/ContactForm7FormCest.php +++ b/tests/acceptance/integrations/other/ContactForm7FormCest.php @@ -186,6 +186,162 @@ function($I) { $I->apiCheckSubscriberExists($I, $emailAddress); } + /** + * Test that saving a Contact Form 7 to ConvertKit Tag Mapping works. + * + * @since 2.5.2 + * + * @param AcceptanceTester $I Tester. + */ + public function testSettingsContactForm7ToConvertKitTagMapping(AcceptanceTester $I) + { + // Setup ConvertKit Plugin. + $I->setupConvertKitPlugin($I); + $I->setupConvertKitPluginResources($I); + + // Create Contact Form 7 Form. + $contactForm7ID = $this->_createContactForm7Form($I); + + // Load Contact Form 7 Plugin Settings. + $I->amOnAdminPage('options-general.php?page=_wp_convertkit_settings&tab=contactform7'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Check that a Form Mapping option is displayed. + $I->seeElementInDOM('#_wp_convertkit_integration_contactform7_settings_' . $contactForm7ID); + + // Change Form to value specified in the .env file. + $I->selectOption('#_wp_convertkit_integration_contactform7_settings_' . $contactForm7ID, $_ENV['CONVERTKIT_API_TAG_NAME']); + + $I->click('Save Changes'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Check the value of the Form field matches the input provided. + $I->seeOptionIsSelected('#_wp_convertkit_integration_contactform7_settings_' . $contactForm7ID, $_ENV['CONVERTKIT_API_TAG_NAME']); + + // Create Page with Contact Form 7 Shortcode. + $I->havePageInDatabase( + [ + 'post_title' => 'ConvertKit: Contact Form 7 Shortcode: Tag', + 'post_name' => 'convertkit-contact-form-7-shortcode-tag', + 'post_content' => 'Form: +[contact-form-7 id="' . $contactForm7ID . '"]', + ] + ); + + // Load the Page on the frontend site. + $I->amOnPage('/convertkit-contact-form-7-shortcode-tag'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Define email address for this test. + $emailAddress = $I->generateEmailAddress(); + + // Complete Name and Email. + $I->fillField('input[name=your-name]', 'ConvertKit Name'); + $I->fillField('input[name=your-email]', $emailAddress); + $I->fillField('input[name=your-subject]', 'ConvertKit Subject'); + + // Submit Form. + $I->click('Submit'); + + // Confirm the form submitted without errors. + $I->performOn( + 'form.sent', + function($I) { + $I->see('Thank you for your message. It has been sent.'); + } + ); + + // Confirm that the email address was added to ConvertKit. + $subscriberID = $I->apiCheckSubscriberExists($I, $emailAddress); + + // Check that the subscriber has been assigned to the tag. + $I->apiCheckSubscriberHasTag($I, $subscriberID, $_ENV['CONVERTKIT_API_TAG_ID']); + } + + /** + * Test that saving a Contact Form 7 to ConvertKit Sequence Mapping works. + * + * @since 2.5.2 + * + * @param AcceptanceTester $I Tester. + */ + public function testSettingsContactForm7ToConvertKitSequenceMapping(AcceptanceTester $I) + { + // Setup ConvertKit Plugin. + $I->setupConvertKitPlugin($I); + $I->setupConvertKitPluginResources($I); + + // Create Contact Form 7 Form. + $contactForm7ID = $this->_createContactForm7Form($I); + + // Load Contact Form 7 Plugin Settings. + $I->amOnAdminPage('options-general.php?page=_wp_convertkit_settings&tab=contactform7'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Check that a Form Mapping option is displayed. + $I->seeElementInDOM('#_wp_convertkit_integration_contactform7_settings_' . $contactForm7ID); + + // Change Form to value specified in the .env file. + $I->selectOption('#_wp_convertkit_integration_contactform7_settings_' . $contactForm7ID, $_ENV['CONVERTKIT_API_SEQUENCE_NAME']); + + $I->click('Save Changes'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Check the value of the Form field matches the input provided. + $I->seeOptionIsSelected('#_wp_convertkit_integration_contactform7_settings_' . $contactForm7ID, $_ENV['CONVERTKIT_API_SEQUENCE_NAME']); + + // Create Page with Contact Form 7 Shortcode. + $I->havePageInDatabase( + [ + 'post_title' => 'ConvertKit: Contact Form 7 Shortcode: Sequence', + 'post_name' => 'convertkit-contact-form-7-shortcode-sequence', + 'post_content' => 'Form: +[contact-form-7 id="' . $contactForm7ID . '"]', + ] + ); + + // Load the Page on the frontend site. + $I->amOnPage('/convertkit-contact-form-7-shortcode-sequence'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Define email address for this test. + $emailAddress = $I->generateEmailAddress(); + + // Complete Name and Email. + $I->fillField('input[name=your-name]', 'ConvertKit Name'); + $I->fillField('input[name=your-email]', $emailAddress); + $I->fillField('input[name=your-subject]', 'ConvertKit Subject'); + + // Submit Form. + $I->click('Submit'); + + // Confirm the form submitted without errors. + $I->performOn( + 'form.sent', + function($I) { + $I->see('Thank you for your message. It has been sent.'); + } + ); + + // Confirm that the email address was added to ConvertKit. + $subscriberID = $I->apiCheckSubscriberExists($I, $emailAddress); + + // Check that the subscriber has been assigned to the sequence. + $I->apiCheckSubscriberHasSequence($I, $subscriberID, $_ENV['CONVERTKIT_API_SEQUENCE_ID']); + } + /** * Test that setting a Contact Form 7 Form to the '(Do not subscribe)' option works. * @@ -471,6 +627,43 @@ private function _createContactForm7Form(AcceptanceTester $I) ); } + /** + * Tests that existing settings are automatically migrated when updating + * the Plugin to 2.5.2 or higher. + * + * @since 2.5.2 + * + * @param AcceptanceTester $I Tester. + */ + public function testSettingsMigratedOnUpgrade(AcceptanceTester $I) + { + // Create settings as if they were created / edited when the ConvertKit Plugin < 2.5.2 + // was active. + $I->haveOptionInDatabase( + '_wp_convertkit_integration_contactform7_settings', + [ + '1' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'creator_network_recommendations_1' => '1', + '2' => '', + ] + ); + + // Downgrade the Plugin version to simulate an upgrade. + $I->haveOptionInDatabase('convertkit_version', '2.4.9'); + + // Load admin screen. + $I->amOnAdminPage('index.php'); + + // Check settings structure has been updated. + $settings = $I->grabOptionFromDatabase('_wp_convertkit_integration_contactform7_settings'); + $I->assertArrayHasKey('1', $settings); + $I->assertArrayHasKey('creator_network_recommendations_1', $settings); + $I->assertArrayHasKey('2', $settings); + $I->assertEquals($settings['1'], 'form:' . $_ENV['CONVERTKIT_API_FORM_ID']); + $I->assertEquals($settings['creator_network_recommendations_1'], '1'); + $I->assertEquals($settings['2'], ''); + } + /** * Deactivate and reset Plugin(s) after each test, if the test passes. * We don't use _after, as this would provide a screenshot of the Plugin diff --git a/views/backend/subscription-dropdown-field.php b/views/backend/subscription-dropdown-field.php index 5b6eebd5..346fa89f 100644 --- a/views/backend/subscription-dropdown-field.php +++ b/views/backend/subscription-dropdown-field.php @@ -19,14 +19,14 @@ - + exist() ) { foreach ( $forms->get() as $form ) { printf( '', - esc_attr( $form['id'] ), - selected( $form['id'], $value, false ), + esc_attr( 'form:' . $form['id'] ), + selected( 'form:' . $form['id'], $value, false ), esc_attr( $form['name'] ), ( ! empty( $form['format'] ) ? esc_attr( $form['format'] ) : 'inline' ) ); @@ -34,4 +34,34 @@ } ?> + + + exist() ) { + foreach ( $sequences->get() as $sequence ) { + printf( + '', + esc_attr( 'sequence:' . $sequence['id'] ), + selected( 'sequence:' . $sequence['id'], $value, false ), + esc_attr( $sequence['name'] ) + ); + } + } + ?> + + + + exist() ) { + foreach ( $tags->get() as $convertkit_tag ) { + printf( + '', + esc_attr( 'tag:' . $convertkit_tag['id'] ), + selected( 'tag:' . $convertkit_tag['id'], $value, false ), + esc_attr( $convertkit_tag['name'] ) + ); + } + } + ?> + diff --git a/wp-convertkit.php b/wp-convertkit.php index 4c022e5c..2e8a59ec 100644 --- a/wp-convertkit.php +++ b/wp-convertkit.php @@ -66,6 +66,7 @@ require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-resource-landing-pages.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-resource-posts.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-resource-products.php'; +require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-resource-sequences.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-resource-tags.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-settings.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/class-convertkit-settings-broadcasts.php';