diff --git a/.github/workflows/_run-tests.yml b/.github/workflows/_run-tests.yml index aa1c0b30c..934923c06 100644 --- a/.github/workflows/_run-tests.yml +++ b/.github/workflows/_run-tests.yml @@ -79,7 +79,7 @@ jobs: steps: - name: Define Test Group Name id: test-group - uses: mad9000/actions-find-and-replace-string@5 + uses: mad9000/actions-find-and-replace-string@6 with: source: ${{ inputs.test-group }} find: '/' @@ -272,29 +272,26 @@ jobs: # Write any secrets, such as API keys, to the .env.testing file now. - name: Define GitHub Secrets in .env.testing - uses: DamianReeves/write-file-action@v1.3 - with: - path: ${{ env.PLUGIN_DIR }}/.env.testing - contents: | - WORDPRESS_V3_BLOCK_EDITOR_ENABLED=${{ env.WORDPRESS_V3_BLOCK_EDITOR_ENABLED }} - WORDPRESS_DB_SQL_DUMP_FILE=${{ env.WORDPRESS_DB_SQL_DUMP_FILE }} - CONVERTKIT_API_KEY=${{ env.CONVERTKIT_API_KEY }} - CONVERTKIT_API_SECRET=${{ env.CONVERTKIT_API_SECRET }} - CONVERTKIT_API_KEY_NO_DATA=${{ env.CONVERTKIT_API_KEY_NO_DATA }} - CONVERTKIT_API_SECRET_NO_DATA=${{ env.CONVERTKIT_API_SECRET_NO_DATA }} - CONVERTKIT_OAUTH_ACCESS_TOKEN=${{ env.CONVERTKIT_OAUTH_ACCESS_TOKEN }} - CONVERTKIT_OAUTH_REFRESH_TOKEN=${{ env.CONVERTKIT_OAUTH_REFRESH_TOKEN }} - CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=${{ env.CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA }} - CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=${{ env.CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA }} - CONVERTKIT_OAUTH_CLIENT_ID=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }} - CONVERTKIT_OAUTH_REDIRECT_URI=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }} - KIT_OAUTH_REDIRECT_URI=${{ env.KIT_OAUTH_REDIRECT_URI }} - CONVERTKIT_API_SIGNED_SUBSCRIBER_ID=${{ env.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID }} - CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS=${{ env.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS }} - CONVERTKIT_API_RECAPTCHA_SITE_KEY=${{ env.CONVERTKIT_API_RECAPTCHA_SITE_KEY }} - CONVERTKIT_API_RECAPTCHA_SECRET_KEY=${{ env.CONVERTKIT_API_RECAPTCHA_SECRET_KEY }} - - write-mode: overwrite + run: | + cat > ${{ env.PLUGIN_DIR }}/.env.testing << 'ENVEOF' + WORDPRESS_V3_BLOCK_EDITOR_ENABLED=${{ env.WORDPRESS_V3_BLOCK_EDITOR_ENABLED }} + WORDPRESS_DB_SQL_DUMP_FILE=${{ env.WORDPRESS_DB_SQL_DUMP_FILE }} + CONVERTKIT_API_KEY=${{ env.CONVERTKIT_API_KEY }} + CONVERTKIT_API_SECRET=${{ env.CONVERTKIT_API_SECRET }} + CONVERTKIT_API_KEY_NO_DATA=${{ env.CONVERTKIT_API_KEY_NO_DATA }} + CONVERTKIT_API_SECRET_NO_DATA=${{ env.CONVERTKIT_API_SECRET_NO_DATA }} + CONVERTKIT_OAUTH_ACCESS_TOKEN=${{ env.CONVERTKIT_OAUTH_ACCESS_TOKEN }} + CONVERTKIT_OAUTH_REFRESH_TOKEN=${{ env.CONVERTKIT_OAUTH_REFRESH_TOKEN }} + CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA=${{ env.CONVERTKIT_OAUTH_ACCESS_TOKEN_NO_DATA }} + CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA=${{ env.CONVERTKIT_OAUTH_REFRESH_TOKEN_NO_DATA }} + CONVERTKIT_OAUTH_CLIENT_ID=${{ env.CONVERTKIT_OAUTH_CLIENT_ID }} + CONVERTKIT_OAUTH_REDIRECT_URI=${{ env.CONVERTKIT_OAUTH_REDIRECT_URI }} + KIT_OAUTH_REDIRECT_URI=${{ env.KIT_OAUTH_REDIRECT_URI }} + CONVERTKIT_API_SIGNED_SUBSCRIBER_ID=${{ env.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID }} + CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS=${{ env.CONVERTKIT_API_SIGNED_SUBSCRIBER_ID_NO_ACCESS }} + CONVERTKIT_API_RECAPTCHA_SITE_KEY=${{ env.CONVERTKIT_API_RECAPTCHA_SITE_KEY }} + CONVERTKIT_API_RECAPTCHA_SECRET_KEY=${{ env.CONVERTKIT_API_RECAPTCHA_SECRET_KEY }} + ENVEOF # Installs wp-browser, Codeception, PHP CodeSniffer and anything else needed to run tests. - name: Run Composer diff --git a/admin/importers/class-convertkit-admin-importer-convertkit-legacy-forms.php b/admin/importers/class-convertkit-admin-importer-convertkit-legacy-forms.php new file mode 100644 index 000000000..fc491d34a --- /dev/null +++ b/admin/importers/class-convertkit-admin-importer-convertkit-legacy-forms.php @@ -0,0 +1,280 @@ +description = __( 'Kit Legacy Forms are being phased out. Use this tool to replace Kit Form shortcodes and blocks using a Legacy Form with a new Kit Form.', 'convertkit' ); + + // Register this as an importer, if ConvertKit Legacy Forms exist. + add_filter( 'convertkit_get_form_importers', array( $this, 'register' ) ); + + } + + /** + * Returns an array of Kit Legacy Forms form IDs and titles. + * + * @since 3.3.5 + * + * @return array + */ + public function get_forms() { + + // Query resource class to fetch legacy forms. + $forms = array(); + $convertkit_forms = new ConvertKit_Resource_Forms( 'settings' ); + if ( $convertkit_forms->exist() ) { + foreach ( $convertkit_forms->get() as $form ) { + // Skip if not a Legacy Form. + if ( ! $convertkit_forms->is_legacy( $form['id'] ) ) { + continue; + } + + $forms[ $form['id'] ] = $form['name']; + } + } + + return $forms; + + } + + /** + * Returns an array of legacy Kit form IDs (as strings) for use in + * filtering shortcodes/blocks that reference them. + * + * @since 3.3.5 + * + * @return array + */ + private function get_legacy_form_ids() { + + $legacy_ids = array(); + $convertkit_forms = new ConvertKit_Resource_Forms( 'settings' ); + + if ( ! $convertkit_forms->exist() ) { + return $legacy_ids; + } + + foreach ( $convertkit_forms->get() as $form ) { + if ( ! $convertkit_forms->is_legacy( $form['id'] ) ) { + continue; + } + $legacy_ids[] = (string) $form['id']; + } + + return $legacy_ids; + + } + + /** + * Overrides the parent method to: + * - return form IDs found in both shortcodes AND blocks (the parent only + * handles shortcodes), and + * - filter the result so only legacy form IDs are returned. + * + * @since 3.3.5 + * + * @param string $content Content containing Kit Form shortcodes / blocks. + * @return array + */ + public function get_form_ids_from_content( $content ) { + + // Get shortcode-derived form IDs from the parent. + $shortcode_ids = parent::get_form_ids_from_content( $content ); + + // Get block-derived form IDs (parent only handles shortcodes). + $block_ids = $this->get_block_form_ids_from_content( $content ); + + // Combine and filter to legacy form IDs only. + $all_ids = array_unique( array_merge( $shortcode_ids, $block_ids ) ); + $legacy_ids = $this->get_legacy_form_ids(); + + // Cast both sides to strings for safe comparison. + $all_ids = array_map( 'strval', $all_ids ); + + return array_values( array_intersect( $all_ids, $legacy_ids ) ); + + } + + /** + * Returns an array of form IDs from convertkit/form blocks in the given + * content. Walks innerBlocks recursively. + * + * @since 3.3.5 + * + * @param string $content Content containing Kit Form blocks. + * @return array + */ + private function get_block_form_ids_from_content( $content ) { + + return $this->extract_block_form_ids( parse_blocks( $content ) ); + + } + + /** + * Recursively walks blocks (and innerBlocks) and returns an array of form + * IDs from any convertkit/form block's `form` attribute. + * + * @since 3.3.5 + * + * @param array $blocks Blocks. + * @return array + */ + private function extract_block_form_ids( $blocks ) { + + $form_ids = array(); + + // Normalise the block ID attribute(s) to an array so we can match + // against multiple attribute names (e.g. both `form` and `id`). + $id_attributes = (array) $this->block_id_attribute; + + foreach ( $blocks as $block ) { + if ( ! empty( $block['innerBlocks'] ) ) { + $form_ids = array_merge( + $form_ids, + $this->extract_block_form_ids( $block['innerBlocks'] ) + ); + } + + if ( $block['blockName'] !== $this->block_name ) { + continue; + } + + // Record the form ID from the first matching attribute. If a block + // somehow has both `form` and `id` set, they'd be the same value, so + // we stop after the first match to avoid double-counting. + foreach ( $id_attributes as $id_attribute ) { + if ( empty( $block['attrs'][ $id_attribute ] ) ) { + continue; + } + $form_ids[] = (string) $block['attrs'][ $id_attribute ]; + break; + } + } + + return $form_ids; + + } + + /** + * Overrides the parent method to only return post IDs whose content + * contains a Kit Form shortcode or block referencing a legacy form ID. + * + * The parent's broad SQL match returns any post containing a + * `[convertkit_form` shortcode or `
Some content with characters !@£$%^&*()_+~!@£$%^&*()_+\\\
', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => '', + 'tag' => '', + ], + ], + ] + ); + } + + /** + * 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 + * deactivation and not the true test error. + * + * @since 3.3.5 + * + * @param EndToEndTester $I Tester. + */ + public function _passed(EndToEndTester $I) + { + $I->deactivateKitPlugin($I); + $I->resetKitPlugin($I); + } +} diff --git a/tests/Integration/ImporterTest.php b/tests/Integration/ImporterTest.php index c11f99668..7ebbc08df 100644 --- a/tests/Integration/ImporterTest.php +++ b/tests/Integration/ImporterTest.php @@ -38,6 +38,126 @@ public function setUp(): void // Activate Plugin. activate_plugins('convertkit/wp-convertkit.php'); + + // Define Forms as if the Forms resource class populated them from the API. + update_option( + 'convertkit_forms', + [ + 3059218 => [ + 'id' => 3059218, + 'name' => 'Auto Confirm Form', + 'created_at' => '2022-03-07T15:57:51Z', + 'type' => 'embed', + 'format' => 'inline', + 'embed_js' => 'https://cheerful-architect-3237.kit.com/bfac9ed794/index.js', + 'embed_url' => 'https://cheerful-architect-3237.kit.com/bfac9ed794', + 'archived' => false, + 'uid' => 'bfac9ed794', + ], + 2765143 => [ + 'id' => 2765143, + 'name' => 'Double Optin Form', + 'created_at' => '2021-11-11T15:31:28Z', + 'type' => 'embed', + 'format' => 'inline', + 'embed_js' => 'https://cheerful-architect-3237.kit.com/a04b384fc6/index.js', + 'embed_url' => 'https://cheerful-architect-3237.kit.com/a04b384fc6', + 'archived' => false, + 'uid' => 'a04b384fc6', + ], + 3003590 => [ + 'id' => 3003590, + 'name' => 'Third Party Integrations Form', + 'created_at' => '2022-02-17T15:05:31.000Z', + 'type' => 'embed', + 'format' => 'inline', + 'embed_js' => 'https://cheerful-architect-3237.kit.com/71cbcc4042/index.js', + 'embed_url' => 'https://cheerful-architect-3237.kit.com/71cbcc4042', + 'archived' => false, + 'uid' => '71cbcc4042', + ], + 2780977 => [ + 'id' => 2780977, + 'name' => 'Modal Form', + 'created_at' => '2021-11-17T04:22:06.000Z', + 'type' => 'embed', + 'format' => 'modal', + 'embed_js' => 'https://cheerful-architect-3237.kit.com/397e876257/index.js', + 'embed_url' => 'https://cheerful-architect-3237.kit.com/397e876257', + 'archived' => false, + 'uid' => '397e876257', + ], + 2780979 => [ + 'id' => 2780979, + 'name' => 'Slide In Form', + 'created_at' => '2021-11-17T04:22:24.000Z', + 'type' => 'embed', + 'format' => 'slide in', + 'embed_js' => 'https://cheerful-architect-3237.kit.com/e0d65bed9d/index.js', + 'embed_url' => 'https://cheerful-architect-3237.kit.com/e0d65bed9d', + 'archived' => false, + 'uid' => 'e0d65bed9d', + ], + 2765139 => [ + 'id' => 2765139, + 'name' => 'Page Form', + 'created_at' => '2021-11-11T15:30:40.000Z', + 'type' => 'embed', + 'format' => 'inline', + 'embed_js' => 'https://cheerful-architect-3237.kit.com/85629c512d/index.js', + 'embed_url' => 'https://cheerful-architect-3237.kit.com/85629c512d', + 'archived' => false, + 'uid' => '85629c512d', + ], + 470099 => [ + 'id' => 470099, + 'name' => 'Legacy Form', + 'created_at' => null, + 'type' => 'embed', + 'url' => 'https://app.kit.com/landing_pages/470099', + 'embed_js' => 'https://api.kit.com/api/v3/forms/470099.js?api_key=' . $_ENV['CONVERTKIT_API_KEY'], + 'embed_url' => 'https://api.kit.com/api/v3/forms/470099.html?api_key=' . $_ENV['CONVERTKIT_API_KEY'], + 'title' => 'Join the newsletter', + 'description' => '

Subscribe to get our latest content by email.

', + 'sign_up_button_text' => 'Subscribe', + 'success_message' => 'Success! Now check your email to confirm your subscription.', + 'archived' => false, + ], + 2780980 => [ + 'id' => 2780980, + 'name' => 'Sticky Bar Form', + 'created_at' => '2021-11-17T04:22:42.000Z', + 'type' => 'embed', + 'format' => 'sticky bar', + 'embed_js' => 'https://cheerful-architect-3237.kit.com/9f5c601482/index.js', + 'embed_url' => 'https://cheerful-architect-3237.kit.com/9f5c601482', + 'archived' => false, + 'uid' => '9f5c601482', + ], + 3437554 => [ + 'id' => 3437554, + 'name' => 'AAA Test', + 'created_at' => '2022-07-15T15:06:32.000Z', + 'type' => 'embed', + 'format' => 'inline', + 'embed_js' => 'https://cheerful-architect-3237.kit.com/3bb15822a2/index.js', + 'embed_url' => 'https://cheerful-architect-3237.kit.com/3bb15822a2', + 'archived' => false, + 'uid' => '3bb15822a2', + ], + 2765149 => [ + 'id' => 2765149, + 'name' => 'WooCommerce Product Form', + 'created_at' => '2021-11-11T15:32:54.000Z', + 'type' => 'embed', + 'format' => 'inline', + 'embed_js' => 'https://cheerful-architect-3237.kit.com/7e238f3920/index.js', + 'embed_url' => 'https://cheerful-architect-3237.kit.com/7e238f3920', + 'archived' => false, + 'uid' => '7e238f3920', + ], + ] + ); } /** @@ -53,6 +173,9 @@ public function tearDown(): void // Deactivate Plugin. deactivate_plugins('convertkit/wp-convertkit.php'); + // Delete the Forms resource. + delete_option( 'convertkit_forms' ); + parent::tearDown(); } @@ -108,21 +231,21 @@ public function testActiveCampaignReplaceShortcodesInContent() // Test each shortcode is replaced with the Kit form shortcode. foreach ( $shortcodes as $shortcode ) { $this->assertEquals( - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $shortcode, 1, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode.'; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', $this->importer->replace_shortcodes_in_content( $content, 1, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content and duplicate the shortcode. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode: ' . $shortcode; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $content, 1, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); } @@ -143,7 +266,7 @@ public function testActiveCampaignReplaceShortcodesInContentIgnoringOtherShortco // Define the shortcodes to test. $shortcodes = [ - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', '[a_random_shortcode]', ]; @@ -272,21 +395,21 @@ public function testAWeberReplaceShortcodesInContent() // Test each shortcode is replaced with the Kit form shortcode. foreach ( $shortcodes as $shortcode ) { $this->assertEquals( - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $shortcode, 10, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode.'; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', $this->importer->replace_shortcodes_in_content( $content, 10, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content and duplicate the shortcode. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode: ' . $shortcode; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $content, 10, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); } @@ -307,7 +430,7 @@ public function testAWeberReplaceShortcodesInContentIgnoringOtherShortcodes() // Define the shortcodes to test. $shortcodes = [ - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', '[a_random_shortcode]', ]; @@ -435,21 +558,21 @@ public function testCampaignMonitorReplaceShortcodesInContent() // Test each shortcode is replaced with the Kit form shortcode. foreach ( $shortcodes as $shortcode ) { $this->assertEquals( - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $shortcode, 'cm_6912dba75db2d', $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode.'; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', $this->importer->replace_shortcodes_in_content( $content, 'cm_6912dba75db2d', $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content and duplicate the shortcode. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode: ' . $shortcode; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $content, 'cm_6912dba75db2d', $_ENV['CONVERTKIT_API_FORM_ID'] ) ); } @@ -470,7 +593,7 @@ public function testCampaignMonitorReplaceShortcodesInContentIgnoringOtherShortc // Define the shortcodes to test. $shortcodes = [ - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', '[a_random_shortcode]', ]; @@ -483,6 +606,265 @@ public function testCampaignMonitorReplaceShortcodesInContentIgnoringOtherShortc } } + /** + * Test that the get_form_ids_from_content() method returns Kit legacy form shortcode Form IDs + * ignoring any other shortcodes. + * + * @since 3.3.5 + */ + public function testGetKitLegacyFormsFormIDsFromContent() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_ConvertKit_Legacy_Forms(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Define the content to test. + $content = 'Legacy Form: [convertkit_form form="' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'] . '"] Legacy Form ID: [convertkit_form id="' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'] . '"] non-Legacy Form: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Different shortcode: [aweber formid="12"]'; + + // Extract form IDs from content. + $form_ids = $this->importer->get_form_ids_from_content( $content ); + + // Assert the legacy form shortcode was detected, and the non-legacy form shortcode was ignored. + $this->assertEquals( 1, count( $form_ids ) ); + $this->assertEquals( $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], $form_ids[0] ); + } + + /** + * Test that the replace_shortcodes_in_content() method replaces the Kit legacy form shortcode with the Kit form shortcode. + * + * @since 3.3.5 + */ + public function testKitLegacyFormsReplaceShortcodesInContent() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_ConvertKit_Legacy_Forms(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Define the shortcodes to test. + $shortcodes = [ + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'] . '"]', + '[convertkit_form form=' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'] . ']', + '[convertkit_form id="' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'] . '"]', + '[convertkit_form id=' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'] . ']', + ]; + + // Test each shortcode is replaced with the Kit form shortcode. + foreach ( $shortcodes as $shortcode ) { + $this->assertEquals( + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + $this->importer->replace_shortcodes_in_content( $shortcode, $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], $_ENV['CONVERTKIT_API_FORM_ID'] ) + ); + + // Prepend and append some content. + $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode.'; + $this->assertEquals( + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', + $this->importer->replace_shortcodes_in_content( $content, $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], $_ENV['CONVERTKIT_API_FORM_ID'] ) + ); + + // Prepend and append some content and duplicate the shortcode. + $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode: ' . $shortcode; + $this->assertEquals( + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + $this->importer->replace_shortcodes_in_content( $content, $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], $_ENV['CONVERTKIT_API_FORM_ID'] ) + ); + } + } + + /** + * Test that the replace_shortcodes_in_content() method ignores non-Kit legacy form shortcodes. + * + * @since 3.3.5 + */ + public function testKitLegacyFormsReplaceShortcodesInContentIgnoringOtherShortcodes() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_ConvertKit_Legacy_Forms(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Define the shortcodes to test. + $shortcodes = [ + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[a_random_shortcode]', + ]; + + // Test each shortcode is ignored. + foreach ( $shortcodes as $shortcode ) { + $this->assertEquals( + $shortcode, + $this->importer->replace_shortcodes_in_content( $shortcode, $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], $_ENV['CONVERTKIT_API_FORM_ID'] ) + ); + } + } + + /** + * Test that the replace_blocks_in_post() method replaces the third party form block with the Kit form block, + * and special characters are not stripped when the Post is saved. + * + * @since 3.3.5 + */ + public function testKitLegacyFormsReplaceBlocksInPost() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_ConvertKit_Legacy_Forms(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Create a Post with a MC4WP form block and HTML block, as if the user already created this post. + $postID = $this->factory->post->create( + [ + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_title' => 'Kit Legacy Forms: Replace Blocks in Post', + 'post_content' => str_replace( '\\', '\\\\', '' . $this->html_block ), + ] + ); + + // Replace the blocks in the post. + $this->importer->replace_blocks_in_post( $postID, $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], $_ENV['CONVERTKIT_API_FORM_ID'] ); + + // Test the block is replaced with the Kit form block, and special characters are not stripped. + $this->assertEquals( + '' . $this->html_block, + get_post_field( 'post_content', $postID ) + ); + } + + /** + * Test that the replace_blocks_in_content() method replaces the third party form block with the Kit form block, + * and special characters are not stripped. + * + * @since 3.3.5 + */ + public function testKitLegacyFormsReplaceBlocksInContent() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_ConvertKit_Legacy_Forms(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Define the blocks to test. + $content = '' . $this->html_block; + + // Test the block is replaced with the Kit form block. + $this->assertEquals( + '' . $this->html_block, + $this->importer->replace_blocks_in_content( parse_blocks( $content ), $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], $_ENV['CONVERTKIT_API_FORM_ID'] ) + ); + } + + /** + * Test that get_form_ids_from_content() returns Kit legacy form block Form IDs + * (in addition to shortcode Form IDs), ignoring any non-legacy Kit form blocks. + * + * Exercises the block-extraction extension to the get_form_ids_from_content() + * override on the Kit Legacy Forms importer. + * + * @since 3.3.5 + */ + public function testGetKitLegacyFormsFormIDsFromContentForBlocks() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_ConvertKit_Legacy_Forms(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Define content containing both legacy and non-legacy Kit form blocks, + // plus a legacy shortcode, to confirm both block and shortcode IDs are + // surfaced when they reference a legacy form. + $content = '' + . '' + . '[convertkit_form form="' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'] . '"]'; + + // Extract form IDs from content. + $form_ids = $this->importer->get_form_ids_from_content( $content ); + + // Assert only the legacy form ID is returned (deduplicated across the + // shortcode and block references). + $this->assertCount( 1, $form_ids ); + $this->assertEquals( (string) $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], $form_ids[0] ); + } + + /** + * Test that get_forms_in_posts() only returns post IDs whose content + * contains a Kit Form shortcode or block referencing a legacy form ID. + * + * Exercises the get_forms_in_posts() override on the Kit Legacy Forms + * importer, which narrows the parent class's broad SQL match. + * + * @since 3.3.5 + */ + public function testGetKitLegacyFormsInPosts() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_ConvertKit_Legacy_Forms(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Create a post containing a non-legacy Kit Form shortcode. + $nonLegacyShortcodePostID = $this->factory->post->create( + [ + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_title' => 'Kit Legacy Forms: Non-Legacy Shortcode Post', + 'post_content' => '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + ] + ); + + // Create a post containing a non-legacy Kit Form block. + $nonLegacyBlockPostID = $this->factory->post->create( + [ + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_title' => 'Kit Legacy Forms: Non-Legacy Block Post', + 'post_content' => '', + ] + ); + + // Create a post containing a legacy Kit Form shortcode. + $legacyShortcodePostID = $this->factory->post->create( + [ + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_title' => 'Kit Legacy Forms: Legacy Shortcode Post', + 'post_content' => '[convertkit_form form="' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'] . '"]', + ] + ); + + // Create a post containing a legacy Kit Form block. + $legacyBlockPostID = $this->factory->post->create( + [ + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_title' => 'Kit Legacy Forms: Legacy Block Post', + 'post_content' => '', + ] + ); + + // Fetch the list of post IDs that contain a legacy Kit form reference. + $post_ids = $this->importer->get_forms_in_posts(); + + // Cast post IDs to ints for comparison (wpdb returns strings). + $post_ids = array_map( 'intval', $post_ids ); + + // Assert only the legacy posts are returned, and the non-legacy posts are not. + $this->assertContains( $legacyShortcodePostID, $post_ids ); + $this->assertContains( $legacyBlockPostID, $post_ids ); + $this->assertNotContains( $nonLegacyShortcodePostID, $post_ids ); + $this->assertNotContains( $nonLegacyBlockPostID, $post_ids ); + } + /** * Test that the get_form_ids_from_content() method returns MC4WP form shortcode Form IDs * ignoring any other shortcodes. @@ -539,21 +921,21 @@ public function testMC4WPReplaceShortcodesInContent() // Test each shortcode is replaced with the Kit form shortcode. foreach ( $shortcodes as $shortcode ) { $this->assertEquals( - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $shortcode, 10, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode.'; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', $this->importer->replace_shortcodes_in_content( $content, 10, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content and duplicate the shortcode. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode: ' . $shortcode; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $content, 10, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); } @@ -574,7 +956,7 @@ public function testMC4WPReplaceShortcodesInContentIgnoringOtherShortcodes() // Define the shortcodes to test. $shortcodes = [ - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', '[a_random_shortcode]', ]; @@ -693,21 +1075,21 @@ public function testMailPoetReplaceShortcodesInContent() // Test each shortcode is replaced with the Kit form shortcode. foreach ( $shortcodes as $shortcode ) { $this->assertEquals( - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $shortcode, 10, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode.'; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', $this->importer->replace_shortcodes_in_content( $content, 10, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content and duplicate the shortcode. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode: ' . $shortcode; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $content, 10, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); } @@ -728,7 +1110,7 @@ public function testMailPoetReplaceShortcodesInContentIgnoringOtherShortcodes() // Define the shortcodes to test. $shortcodes = [ - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', '[a_random_shortcode]', ]; @@ -846,21 +1228,21 @@ public function testNewsletterReplaceShortcodesInContent() // Test each shortcode is replaced with the Kit form shortcode. foreach ( $shortcodes as $shortcode ) { $this->assertEquals( - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $shortcode, 0, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode.'; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode.', $this->importer->replace_shortcodes_in_content( $content, 0, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); // Prepend and append some content and duplicate the shortcode. $content = 'Some content before the shortcode: ' . $shortcode . ' Some content after the shortcode: ' . $shortcode; $this->assertEquals( - 'Some content before the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + 'Some content before the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"] Some content after the shortcode: [convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', $this->importer->replace_shortcodes_in_content( $content, 0, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); } @@ -881,7 +1263,7 @@ public function testNewsletterReplaceShortcodesInContentIgnoringOtherShortcodes( // Define the shortcodes to test. $shortcodes = [ - '[convertkit_form id="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', + '[convertkit_form form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', '[a_random_shortcode]', ]; diff --git a/tests/Support/Data/dump.sql b/tests/Support/Data/dump.sql index 142f4cf2a..3f24ebc81 100644 --- a/tests/Support/Data/dump.sql +++ b/tests/Support/Data/dump.sql @@ -386,7 +386,8 @@ INSERT INTO `wp_usermeta` (`umeta_id`, `user_id`, `meta_key`, `meta_value`) VALU (20, 1, 'edit_page_per_page', '100'), (21, 1, 'edit_post_per_page', '100'), (22, 1, 'wp_persisted_preferences', 'a:4:{s:4:"core";a:2:{s:26:"isComplementaryAreaVisible";b:1;s:24:"enableChoosePatternModal";b:0;}s:14:"core/edit-post";a:4:{s:12:"welcomeGuide";b:0;s:23:"metaBoxesMainOpenHeight";i:540;s:20:"welcomeGuideTemplate";b:0;s:19:"metaBoxesMainIsOpen";b:1;}s:9:"_modified";s:24:"2024-07-18T02:45:41.491Z";s:17:"core/edit-widgets";a:2:{s:26:"isComplementaryAreaVisible";b:1;s:12:"welcomeGuide";b:0;}}'), -(23, 1, 'elementor_introduction', 'a:5:{s:7:"exit_to";b:1;s:27:"ai-get-started-announcement";b:1;s:20:"globals_introduction";b:1;s:6:"e-apps";b:1;s:27:"e-editor-one-notice-pointer";b:1;}'); +(23, 1, 'elementor_introduction', 'a:5:{s:7:"exit_to";b:1;s:27:"ai-get-started-announcement";b:1;s:20:"globals_introduction";b:1;s:6:"e-apps";b:1;s:27:"e-editor-one-notice-pointer";b:1;}'), +(24, 1, 'dismissed_no_secure_connection_notice', 'true'); DROP TABLE IF EXISTS `wp_users`; CREATE TABLE `wp_users` ( diff --git a/tests/Support/Helper/WPClassicEditor.php b/tests/Support/Helper/WPClassicEditor.php index 0d1f5c97b..c8a1aca82 100644 --- a/tests/Support/Helper/WPClassicEditor.php +++ b/tests/Support/Helper/WPClassicEditor.php @@ -300,6 +300,9 @@ public function publishAndViewClassicEditorPage($I) // Publish Page. $I->publishClassicEditorPage($I); + // Wait for the notice to display. + $I->waitForElementVisible('.notice-success'); + // Load the Page on the frontend site. $I->click('.notice-success a'); diff --git a/tests/Support/Helper/WPQuickEdit.php b/tests/Support/Helper/WPQuickEdit.php index ee921e43e..74002cf9c 100644 --- a/tests/Support/Helper/WPQuickEdit.php +++ b/tests/Support/Helper/WPQuickEdit.php @@ -61,13 +61,23 @@ public function openQuickEdit($I, $postType, $postID) // Navigate to Post Type's WP_List_Table. $I->amOnAdminPage('edit.php?post_type=' . $postType); - // Hover mouse over Post's table row. - $I->moveMouseOver('tr#post-' . $postID); + // Wait for the WP_List_Table to load. + $I->waitForElementVisible('table.wp-list-table'); - // Wait for Quick edit link to be visible. - $I->waitForElementVisible('tr#post-' . $postID . ' button.editinline'); + // Wait for the Post's row to exist in the table. + $I->waitForElementVisible('tr#post-' . $postID); - // Click Quick Edit link. + // Display all row actions by adding the `visible` class, as + // moveMouseOver() is flaky. + $I->executeJS( + ' + document.querySelectorAll(".row-actions").forEach(function(el){ + el.classList.add("visible"); + }); + ' + ); + + // Click Quick Edit link for the Post. $I->click('tr#post-' . $postID . ' button.editinline'); } } diff --git a/views/backend/settings/tools.php b/views/backend/settings/tools.php index 728fe380a..315fa3ada 100644 --- a/views/backend/settings/tools.php +++ b/views/backend/settings/tools.php @@ -118,11 +118,15 @@


diff --git a/wp-convertkit.php b/wp-convertkit.php index 4b8eb1acc..c2afddcf5 100644 --- a/wp-convertkit.php +++ b/wp-convertkit.php @@ -121,6 +121,7 @@ require_once CONVERTKIT_PLUGIN_PATH . '/admin/importers/class-convertkit-admin-importer-activecampaign.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/importers/class-convertkit-admin-importer-aweber.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/importers/class-convertkit-admin-importer-campaignmonitor.php'; +require_once CONVERTKIT_PLUGIN_PATH . '/admin/importers/class-convertkit-admin-importer-convertkit-legacy-forms.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/importers/class-convertkit-admin-importer-mc4wp.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/importers/class-convertkit-admin-importer-mailpoet.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/importers/class-convertkit-admin-importer-newsletter.php';