From 0bd94b332577191ace866502e6ad100a362fa2de Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 20 Jan 2026 17:36:13 +0800 Subject: [PATCH] Importers: MailPoet --- ...ass-convertkit-admin-importer-mailpoet.php | 82 +++++ .../class-convertkit-admin-section-tools.php | 43 ++- ...luginSettingsToolsImporterMailPoetCest.php | 292 ++++++++++++++++++ tests/Integration/ImporterTest.php | 154 +++++++++ tests/Support/Data/dump.sql | 13 + views/backend/settings/tools.php | 57 ++++ wp-convertkit.php | 1 + 7 files changed, 640 insertions(+), 2 deletions(-) create mode 100644 admin/importers/class-convertkit-admin-importer-mailpoet.php create mode 100644 tests/EndToEnd/general/plugin-screens/PluginSettingsToolsImporterMailPoetCest.php diff --git a/admin/importers/class-convertkit-admin-importer-mailpoet.php b/admin/importers/class-convertkit-admin-importer-mailpoet.php new file mode 100644 index 000000000..27dc10954 --- /dev/null +++ b/admin/importers/class-convertkit-admin-importer-mailpoet.php @@ -0,0 +1,82 @@ +get_results( + "SELECT id, name FROM {$wpdb->prefix}mailpoet_forms WHERE deleted_at IS NULL" + ); + + if ( empty( $results ) ) { + return array(); + } + + $forms = array(); + foreach ( $results as $form ) { + $forms[ $form->id ] = $form->name; + } + + return $forms; + + } + +} diff --git a/admin/section/class-convertkit-admin-section-tools.php b/admin/section/class-convertkit-admin-section-tools.php index b13956391..a37447258 100644 --- a/admin/section/class-convertkit-admin-section-tools.php +++ b/admin/section/class-convertkit-admin-section-tools.php @@ -59,6 +59,7 @@ public function register_notices( $notices ) { 'import_configuration_success' => __( 'Configuration imported successfully.', 'convertkit' ), 'migrate_aweber_configuration_success' => __( 'AWeber forms migrated successfully.', 'convertkit' ), 'migrate_mc4wp_configuration_success' => __( 'MC4WP forms migrated successfully.', 'convertkit' ), + 'migrate_mailpoet_configuration_success' => __( 'MailPoet forms migrated successfully.', 'convertkit' ), ) ); @@ -79,6 +80,7 @@ private function maybe_perform_actions() { $this->maybe_import_configuration(); $this->maybe_migrate_aweber_configuration(); $this->maybe_migrate_mc4wp_configuration(); + $this->maybe_migrate_mailpoet_configuration(); } @@ -390,6 +392,42 @@ private function maybe_migrate_mc4wp_configuration() { } + /** + * Replaces Mailpoet Form Shortcodes and Blocks with Kit Form Shortcodes and Blocks, if the user submitted the + * Mailpoet Migrate Configuration section. + * + * @since 3.1.6 + */ + private function maybe_migrate_mailpoet_configuration() { + + // Bail if nonce verification fails. + if ( ! isset( $_REQUEST['_convertkit_settings_tools_nonce'] ) ) { + return; + } + + if ( ! wp_verify_nonce( sanitize_key( $_REQUEST['_convertkit_settings_tools_nonce'] ), 'convertkit-settings-tools' ) ) { + return; + } + + // Bail if no Mailpoet Form IDs were submitted. + if ( ! isset( $_REQUEST['_wp_convertkit_integration_mailpoet_settings'] ) ) { + return; + } + + // Initialise the importer. + $mailpoet = new ConvertKit_Admin_Importer_Mailpoet(); + + // Iterate through the Mailpoet Form IDs and replace the shortcodes with the Kit Form Shortcodes. + foreach ( array_map( 'sanitize_text_field', wp_unslash( $_REQUEST['_wp_convertkit_integration_mailpoet_settings'] ) ) as $mailpoet_form_id => $kit_form_id ) { + $mailpoet->replace_blocks_in_posts( (int) $mailpoet_form_id, (int) $kit_form_id ); + $mailpoet->replace_shortcodes_in_posts( (int) $mailpoet_form_id, (int) $kit_form_id ); + } + + // Redirect to Tools screen. + $this->redirect_with_success_notice( 'migrate_mailpoet_configuration_success' ); + + } + /** * Outputs the Debug Log and System Info view. * @@ -412,8 +450,9 @@ public function render() { $forms = new ConvertKit_Resource_Forms(); // Get Importers. - $aweber = new ConvertKit_Admin_Importer_AWeber(); - $mc4wp = new ConvertKit_Admin_Importer_MC4WP(); + $aweber = new ConvertKit_Admin_Importer_AWeber(); + $mc4wp = new ConvertKit_Admin_Importer_MC4WP(); + $mailpoet = new ConvertKit_Admin_Importer_Mailpoet(); // Output view. require_once CONVERTKIT_PLUGIN_PATH . '/views/backend/settings/tools.php'; diff --git a/tests/EndToEnd/general/plugin-screens/PluginSettingsToolsImporterMailPoetCest.php b/tests/EndToEnd/general/plugin-screens/PluginSettingsToolsImporterMailPoetCest.php new file mode 100644 index 000000000..781348f14 --- /dev/null +++ b/tests/EndToEnd/general/plugin-screens/PluginSettingsToolsImporterMailPoetCest.php @@ -0,0 +1,292 @@ + Kit > Tools > Import sections for the MailPoet third party form plugin. + * + * @since 3.1.6 + */ +class PluginSettingsToolsImporterMailPoetCest +{ + /** + * Run common actions before running the test functions in this class. + * + * @since 3.1.6 + * + * @param EndToEndTester $I Tester. + */ + public function _before(EndToEndTester $I) + { + // Activate Plugins. + $I->activateKitPlugin($I); + } + + /** + * Test that MailPoet Form Shortcodes are replaced with Kit Form Shortcodes when the Tools > MailPoet: Migrate Configuration is configured. + * + * @since 3.1.6 + * + * @param EndToEndTester $I Tester. + */ + public function testMailPoetImportWithShortcodes(EndToEndTester $I) + { + // Setup Plugin. + $I->setupKitPlugin($I); + $I->setupKitPluginResources($I); + + // Create MailPoet Forms. + $mailPoetFormIDs = $this->_createMailPoetForms($I); + + // Insert MailPoet Form Shortcodes into Pages. + $pageIDs = $this->_createPagesWithMailPoetFormShortcodes($I, $mailPoetFormIDs); + + // Navigate to the Tools screen. + $I->loadKitSettingsToolsScreen($I); + + // Select the Kit Forms to replace the MailPoet Forms. + foreach ($mailPoetFormIDs as $mailPoetFormID) { + $I->selectOption('_wp_convertkit_integration_mailpoet_settings[' . $mailPoetFormID . ']', $_ENV['CONVERTKIT_API_FORM_ID']); + } + + // Click the Migrate button. + $I->click('Migrate'); + + // Confirm success message displays. + $I->waitForElementVisible('.notice-success'); + $I->see('MailPoet forms migrated successfully.'); + + // View the Pages, to confirm Kit Forms now display. + foreach ($pageIDs as $pageID) { + $I->amOnPage('?p=' . $pageID); + $I->seeElementInDOM('form[data-sv-form]'); + } + } + + /** + * Test that MailPoet Blocks are replaced with Kit Blocks when the Tools > MailPoet: Migrate Configuration is configured. + * + * @since 3.1.6 + * + * @param EndToEndTester $I Tester. + */ + public function testMailPoetImportWithBlocks(EndToEndTester $I) + { + // Setup Plugin. + $I->setupKitPlugin($I); + $I->setupKitPluginResources($I); + + // Create MailPoet Forms. + $mailPoetFormIDs = $this->_createMailPoetForms($I); + + // Insert MailPoet Blocks into Pages. + $pageIDs = $this->_createPagesWithMailPoetBlocks($I, $mailPoetFormIDs); + + // Navigate to the Tools screen. + $I->loadKitSettingsToolsScreen($I); + + // Select the Kit Forms to replace the MailPoet Forms. + foreach ($mailPoetFormIDs as $mailPoetFormID) { + $I->selectOption('_wp_convertkit_integration_mailpoet_settings[' . $mailPoetFormID . ']', $_ENV['CONVERTKIT_API_FORM_ID']); + } + + // Click the Migrate button. + $I->click('Migrate'); + + // Confirm success message displays. + $I->waitForElementVisible('.notice-success'); + $I->see('MailPoet forms migrated successfully.'); + + // Test each Page. + foreach ($pageIDs as $pageID) { + $I->amOnPage('?p=' . $pageID); + + // Check Kit Form block is displayed. + $I->seeElementInDOM('form[data-sv-form]'); + + // Confirm special characters have not been stripped. + $I->seeInSource('!@£$%^&*()_+~!@£$%^&*()_+\\'); + } + } + + /** + * Test that the MailPoet: Migrate Configuration section is not displayed when no MailPoet Forms exist. + * + * @since 3.1.6 + * + * @param EndToEndTester $I Tester. + */ + public function testMailPoetImportWhenNoMailPoetForms(EndToEndTester $I) + { + // Setup Plugin. + $I->setupKitPlugin($I); + $I->setupKitPluginResources($I); + + // Navigate to the Tools screen. + $I->loadKitSettingsToolsScreen($I); + + // Confirm no MailPoet: Migrate Configuration section is displayed. + $I->dontSeeElementInDOM('#import-mailpoet'); + } + + /** + * Test that the MailPoet: Migrate Configuration section is not displayed when MailPoet Forms exist, + * but no Pages, Posts or Custom Posts contain MailPoet Form Shortcodes. + * + * @since 3.1.6 + * + * @param EndToEndTester $I Tester. + */ + public function testMailPoetImportWhenNoMailPoetShortcodesInContent(EndToEndTester $I) + { + // Setup Plugin. + $I->setupKitPlugin($I); + $I->setupKitPluginResources($I); + + // Create MailPoet Forms. + $mailPoetFormIDs = $this->_createMailPoetForms($I); + + // Navigate to the Tools screen. + $I->loadKitSettingsToolsScreen($I); + + // Confirm no MailPoet: Migrate Configuration section is displayed, as there are no + // MailPoet Form Shortcodes in the content. + $I->dontSeeElementInDOM('#import-mailpoet'); + } + + /** + * Test that the MailPoet: Migrate Configuration section is not displayed when no Kit Forms exist. + * + * @since 3.1.6 + * + * @param EndToEndTester $I Tester. + */ + public function testMailPoetImportWhenNoKitForms(EndToEndTester $I) + { + // Setup Plugin. + $I->setupKitPluginCredentialsNoData($I); + $I->setupKitPluginResourcesNoData($I); + + // Navigate to the Tools screen. + $I->loadKitSettingsToolsScreen($I); + + // Confirm no MailPoet: Migrate Configuration section is displayed, as there are no + // MailPoet Form Shortcodes in the content. + $I->dontSeeElementInDOM('#import-mailpoet'); + } + + /** + * Create MailPoet Forms. + * + * @since 3.1.6 + * + * @param EndToEndTester $I Tester. + * @return array + */ + private function _createMailPoetForms(EndToEndTester $I) + { + return array( + $I->haveInDatabase( + 'wp_mailpoet_forms', + [ + 'name' => 'MailPoet Form #1', + ] + ), + $I->haveInDatabase( + 'wp_mailpoet_forms', + [ + 'name' => 'MailPoet Form #2', + ] + ), + ); + } + + /** + * Create Pages with MailPoet Form Shortcodes. + * + * @since 3.1.6 + * + * @param EndToEndTester $I Tester. + * @param array $mailPoetFormIDs MailPoet Form IDs. + * @return array + */ + private function _createPagesWithMailPoetFormShortcodes(EndToEndTester $I, $mailPoetFormIDs) + { + $pageIDs = array(); + + foreach ($mailPoetFormIDs as $mailPoetFormID) { + $pageIDs[] = $I->havePostInDatabase( + [ + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_title' => 'Page with MailPoet Form #' . $mailPoetFormID, + 'post_content' => '[mailpoet_form id="' . $mailPoetFormID . '"]', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => '', + 'tag' => '', + ], + ], + ] + ); + } + + return $pageIDs; + } + + /** + * Create Pages with MailPoet Blocks. + * + * @since 3.1.6 + * + * @param EndToEndTester $I Tester. + * @param array $mailPoetFormIDs MailPoet Form IDs. + * @return array + */ + private function _createPagesWithMailPoetBlocks(EndToEndTester $I, $mailPoetFormIDs) + { + $pageIDs = array(); + + foreach ($mailPoetFormIDs as $mailPoetFormID) { + $pageIDs[] = $I->havePostInDatabase( + [ + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_title' => 'Page with MailPoet Block #' . $mailPoetFormID, + 'post_content' => '
Some content with characters !@£$%^&*()_+~!@£$%^&*()_+\\\
', + + // Configure Kit Plugin to not display a default Form, so we test against the Kit Form in the content. + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => '', + 'tag' => '', + ], + ], + ] + ); + } + + return $pageIDs; + } + + /** + * 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.1.6 + * + * @param EndToEndTester $I Tester. + */ + public function _passed(EndToEndTester $I) + { + // Clear the table of any existing MailPoet Forms. + $I->truncateDbTable('wp_mailpoet_forms'); + $I->deactivateKitPlugin($I); + $I->resetKitPlugin($I); + } +} diff --git a/tests/Integration/ImporterTest.php b/tests/Integration/ImporterTest.php index 886c2a761..f6e1a36f5 100644 --- a/tests/Integration/ImporterTest.php +++ b/tests/Integration/ImporterTest.php @@ -389,4 +389,158 @@ public function testMC4WPReplaceBlocksInContent() $this->importer->replace_blocks_in_content( parse_blocks( $content ), 4410, $_ENV['CONVERTKIT_API_FORM_ID'] ) ); } + + /** + * Test that the get_form_ids_from_content() method returns MailPoet form shortcode Form IDs + * ignoring any other shortcodes. + * + * @since 3.1.6 + */ + public function testGetMailPoetFormIDsFromContent() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_Mailpoet(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Define the content to test. + $content = '[mailpoet_form id="10"] some content [mailpoet_form id="11"] some other content [aweber formid="12"] different shortcode to ignore'; + + // Extract form IDs from content. + $form_ids = $this->importer->get_form_ids_from_content( $content ); + + // Assert the correct number of form IDs are returned. + $this->assertEquals( 2, count( $form_ids ) ); + $this->assertEquals( 10, $form_ids[0] ); + $this->assertEquals( 11, $form_ids[1] ); + } + + /** + * Test that the replace_shortcodes_in_content() method replaces the MailPoet form shortcode with the Kit form shortcode. + * + * @since 3.1.6 + */ + public function testMailPoetReplaceShortcodesInContent() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_Mailpoet(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Define the shortcodes to test. + $shortcodes = [ + '[mailpoet_form id="10"]', + '[mailpoet_form id=10]', + ]; + + // Test each shortcode is replaced with the Kit form shortcode. + foreach ( $shortcodes as $shortcode ) { + $this->assertEquals( + '[convertkit_form id="' . $_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.', + $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'] . '"]', + $this->importer->replace_shortcodes_in_content( $content, 10, $_ENV['CONVERTKIT_API_FORM_ID'] ) + ); + } + } + + /** + * Test that the replace_shortcodes_in_content() method ignores non-MailPoet shortcodes. + * + * @since 3.1.6 + */ + public function testMailPoetReplaceShortcodesInContentIgnoringOtherShortcodes() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_Mailpoet(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Define the shortcodes to test. + $shortcodes = [ + '[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, 10, $_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.1.6 + */ + public function testMailPoetReplaceBlocksInPost() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_Mailpoet(); + + // Confirm initialization didn't result in an error. + $this->assertNotInstanceOf(\WP_Error::class, $this->importer); + + // Create a Post with a MailPoet 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' => 'MailPoet: Replace Blocks in Post', + 'post_content' => str_replace( '\\', '\\\\', '' . $this->html_block ), + ] + ); + + // Replace the blocks in the post. + $this->importer->replace_blocks_in_post( $postID, 4410, $_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.1.6 + */ + public function testMailPoetReplaceBlocksInContent() + { + // Initialize the class we want to test. + $this->importer = new \ConvertKit_Admin_Importer_Mailpoet(); + + // 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 ), 4410, $_ENV['CONVERTKIT_API_FORM_ID'] ) + ); + } } diff --git a/tests/Support/Data/dump.sql b/tests/Support/Data/dump.sql index e3ec007bf..a99f8a98b 100644 --- a/tests/Support/Data/dump.sql +++ b/tests/Support/Data/dump.sql @@ -97,6 +97,19 @@ CREATE TABLE `wp_links` ( KEY `link_visible` (`link_visible`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; +DROP TABLE IF EXISTS `wp_mailpoet_forms`; +CREATE TABLE `wp_mailpoet_forms` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `name` varchar(90) COLLATE utf8mb4_unicode_520_ci NOT NULL, + `status` varchar(20) COLLATE utf8mb4_unicode_520_ci NOT NULL DEFAULT 'enabled', + `body` longtext COLLATE utf8mb4_unicode_520_ci, + `settings` longtext COLLATE utf8mb4_unicode_520_ci, + `styles` longtext COLLATE utf8mb4_unicode_520_ci, + `created_at` timestamp NULL DEFAULT NULL, + `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + `deleted_at` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_520_ci; DROP TABLE IF EXISTS `wp_options`; CREATE TABLE `wp_options` ( diff --git a/views/backend/settings/tools.php b/views/backend/settings/tools.php index 597fb4d72..53f2876cf 100644 --- a/views/backend/settings/tools.php +++ b/views/backend/settings/tools.php @@ -212,6 +212,63 @@ has_forms_in_posts() && $mailpoet->has_forms() && $forms->exist() ) { + ?> +
+

+ +

+
+

+ + + + + + + + + + get_forms() as $mailpoet_form_id => $mailpoet_form_title ) { + ?> + + + + + + +
+ +
+ +

+ +

+
+ diff --git a/wp-convertkit.php b/wp-convertkit.php index f4dbea553..6ef1ab423 100644 --- a/wp-convertkit.php +++ b/wp-convertkit.php @@ -119,6 +119,7 @@ require_once CONVERTKIT_PLUGIN_PATH . '/admin/importers/class-convertkit-admin-importer.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-mc4wp.php'; +require_once CONVERTKIT_PLUGIN_PATH . '/admin/importers/class-convertkit-admin-importer-mailpoet.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/section/class-convertkit-admin-section-base.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/section/class-convertkit-admin-section-broadcasts.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/section/class-convertkit-admin-section-form-entries.php';