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/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';