From 8035b72b75142d7042a25ce50843513e50922e32 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Thu, 30 Jul 2026 13:02:09 +0800 Subject: [PATCH 1/5] Legacy Forms: Add Warning Notice --- ...onvertkit-admin-legacy-resource-notice.php | 265 ++++++++++++++++++ includes/class-wp-convertkit.php | 1 + .../backend/js/legacy-resource-notice.js | 49 ++++ .../other/BlockEditorLegacyNoticeCest.php | 237 ++++++++++++++++ .../other/ClassicEditorLegacyNoticeCest.php | 239 ++++++++++++++++ .../AdminLegacyResourceNoticeTest.php | 228 +++++++++++++++ wp-convertkit.php | 1 + 7 files changed, 1020 insertions(+) create mode 100644 admin/class-convertkit-admin-legacy-resource-notice.php create mode 100644 resources/backend/js/legacy-resource-notice.js create mode 100644 tests/EndToEnd/general/other/BlockEditorLegacyNoticeCest.php create mode 100644 tests/EndToEnd/general/other/ClassicEditorLegacyNoticeCest.php create mode 100644 tests/Integration/AdminLegacyResourceNoticeTest.php diff --git a/admin/class-convertkit-admin-legacy-resource-notice.php b/admin/class-convertkit-admin-legacy-resource-notice.php new file mode 100644 index 000000000..259555f0a --- /dev/null +++ b/admin/class-convertkit-admin-legacy-resource-notice.php @@ -0,0 +1,265 @@ +get_current_post_id(); + if ( ! $post_id ) { + return; + } + + // Get warnings. + $warnings = $this->get_legacy_warnings_for_post_settings( $post_id ); + + // Bail if no warnings are found. + if ( empty( $warnings ) ) { + return; + } + + // Output warnings. + ?> +
+

+ : + +

+ +
+ get_current_post_id(); + if ( ! $post_id ) { + return; + } + + // Get warnings. + $warnings = $this->get_legacy_warnings_for_post_settings( $post_id ); + + // Bail if no warnings are found. + if ( empty( $warnings ) ) { + return; + } + + // Enqueue script. + wp_enqueue_script( + 'convertkit-admin-legacy-resource-notice', + CONVERTKIT_PLUGIN_URL . 'resources/backend/js/legacy-resource-notice.js', + array( 'wp-data', 'wp-notices' ), + CONVERTKIT_PLUGIN_VERSION, + true + ); + + // Localize script. + wp_localize_script( + 'convertkit-admin-legacy-resource-notice', + 'convertkit_legacy_resource_notice', + array( + 'id' => self::GUTENBERG_NOTICE_ID, + 'intro' => __( 'Kit: This page uses one or more legacy Kit resources that are no longer offered for new selections. The following continue to work, but we recommend migrating:', 'convertkit' ), + 'warnings' => $warnings, + ) + ); + + } + + /** + * Returns an array of warning strings for the given Post, if the Post settings + * reference a Legacy Form or Legacy Landing Page. + * + * @since 3.3.7 + * + * @param int $post_id Post ID. + * @return array + */ + private function get_legacy_warnings_for_post_settings( $post_id ) { + + // Get Post settings. + $convertkit_post = new ConvertKit_Post( $post_id ); + $settings = $convertkit_post->get(); + + // Get resources. + $forms = new ConvertKit_Resource_Forms(); + $landing_pages = new ConvertKit_Resource_Landing_Pages(); + + // Initialize warnings array. + $warnings = array(); + + // Form. + if ( $forms->is_legacy( $convertkit_post->get_form() ) ) { + $warnings[] = sprintf( + /* translators: %s: Form name */ + __( 'Form: %s', 'convertkit' ), + $this->get_form_display_name( $convertkit_post->get_form(), $forms ) + ); + } + + // Landing Page. + if ( $landing_pages->is_legacy( $convertkit_post->get_landing_page() ) ) { + $warnings[] = sprintf( + /* translators: %s: Landing page name */ + __( 'Landing Page: %s', 'convertkit' ), + $this->get_landing_page_display_name( $convertkit_post->get_landing_page(), $landing_pages ) + ); + } + + // Restrict Content. + if ( $convertkit_post->get_restrict_content_type() === 'form' && $forms->is_legacy( $convertkit_post->get_restrict_content_id() ) ) { + $warnings[] = sprintf( + /* translators: %s: Form name */ + __( 'Member Content: %s', 'convertkit' ), + $this->get_form_display_name( $convertkit_post->get_restrict_content_id(), $forms ) + ); + } + + return $warnings; + + } + + /** + * Resolves the form ID to a human-readable "Form Name" string, falling + * back to "a Legacy Form" when the resource isn't cached. + * + * @since 3.3.7 + * + * @param int $form_id Form ID. + * @param ConvertKit_Resource_Forms $forms Forms resource class. + * @return string + */ + private function get_form_display_name( $form_id, $forms ) { + + $form = $forms->get_by_id( $form_id ); + if ( $form && ! empty( $form['name'] ) ) { + return sanitize_text_field( $form['name'] ); + } + + return __( 'a Legacy Form', 'convertkit' ); + + } + + /** + * Resolves the landing page identifier (numeric ID or URL string) to a + * human-readable "Landing Page Name" string, falling back to + * "a Legacy Landing Page" when the resource isn't cached. + * + * @since 3.3.7 + * + * @param int|string $id_or_url Landing Page ID or URL. + * @param ConvertKit_Resource_Landing_Pages $landing_pages Landing Pages resource class. + * @return string + */ + private function get_landing_page_display_name( $id_or_url, $landing_pages ) { + + // URL-string legacy assignments (pre-1.9.6) don't have a cached + // resource entry we can look up by ID, so fall back straight away. + if ( is_string( $id_or_url ) && strstr( $id_or_url, 'http' ) ) { + return __( 'a Legacy Landing Page', 'convertkit' ); + } + + $landing_page = $landing_pages->get_by_id( (int) $id_or_url ); + if ( $landing_page && ! empty( $landing_page['name'] ) ) { + return sanitize_text_field( $landing_page['name'] ); + } + + return __( 'a Legacy Landing Page', 'convertkit' ); + + } + + /** + * Returns the current post ID being edited, or 0 if not on a post edit + * screen. Reads $_GET['post'] because $post isn't always set in the + * admin_notices / enqueue_block_editor_assets hook contexts. + * + * @since 3.3.7 + * + * @return int + */ + private function get_current_post_id() { + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( isset( $_GET['post'] ) ) { + return absint( $_GET['post'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended + } + + return 0; + + } + +} diff --git a/includes/class-wp-convertkit.php b/includes/class-wp-convertkit.php index 371ab59d0..922e466b8 100644 --- a/includes/class-wp-convertkit.php +++ b/includes/class-wp-convertkit.php @@ -83,6 +83,7 @@ private function initialize_admin() { $this->classes['admin_cache_plugins'] = new ConvertKit_Admin_Cache_Plugins(); $this->classes['admin_category'] = new ConvertKit_Admin_Category(); $this->classes['admin_landing_page'] = new ConvertKit_Admin_Landing_Page(); + $this->classes['admin_legacy_resource_notice'] = new ConvertKit_Admin_Legacy_Resource_Notice(); $this->classes['admin_importer_activecampaign'] = new ConvertKit_Admin_Importer_ActiveCampaign(); $this->classes['admin_importer_aweber'] = new ConvertKit_Admin_Importer_AWeber(); $this->classes['admin_importer_campaignmonitor'] = new ConvertKit_Admin_Importer_CampaignMonitor(); diff --git a/resources/backend/js/legacy-resource-notice.js b/resources/backend/js/legacy-resource-notice.js new file mode 100644 index 000000000..cf605d543 --- /dev/null +++ b/resources/backend/js/legacy-resource-notice.js @@ -0,0 +1,49 @@ +/** + * Renders a non-dismissible warning notice inside Gutenberg + * when a post/page references a legacy Kit resource. + * + * @author ConvertKit + * @since 3.3.7 + */ + +document.addEventListener('DOMContentLoaded', function () { + // Bail if the notice data is not available. + if (typeof convertkit_legacy_resource_notice === 'undefined') { + return; + } + + // Bail if no warnings are found. + if ( + typeof convertkit_legacy_resource_notice.warnings === 'undefined' || + convertkit_legacy_resource_notice.warnings.length === 0 + ) { + return; + } + + // Bail if Gutenberg is not available. + if ( + typeof wp === 'undefined' || + typeof wp.data === 'undefined' || + typeof wp.data.dispatch !== 'function' + ) { + return; + } + + // Build the notice. + const message = + convertkit_legacy_resource_notice.intro + + '\n' + + convertkit_legacy_resource_notice.warnings + .map(function (warning) { + return '- ' + warning; + }) + .join('\n'); + + // Create the notice. + wp.data + .dispatch('core/notices') + .createWarningNotice(message, { + id: convertkit_legacy_resource_notice.id, + isDismissible: false, + }); +}); diff --git a/tests/EndToEnd/general/other/BlockEditorLegacyNoticeCest.php b/tests/EndToEnd/general/other/BlockEditorLegacyNoticeCest.php new file mode 100644 index 000000000..c6d71c988 --- /dev/null +++ b/tests/EndToEnd/general/other/BlockEditorLegacyNoticeCest.php @@ -0,0 +1,237 @@ +activateKitPlugin($I); + $I->setupKitPlugin($I); + $I->setupKitPluginResources($I); + } + + /** + * Test that a page with a legacy Form assignment displays the warning + * notice inside Gutenberg, listing the legacy form by name. + * + * @since 3.3.7 + * + * @param EndToEndTester $I Tester. + */ + public function testNoticeDisplaysForLegacyForm(EndToEndTester $I) + { + // Create a page with a Legacy Form assignment. + $pageID = $I->havePageInDatabase( + [ + 'post_status' => 'publish', + 'post_title' => 'Kit: Legacy Notice: Gutenberg: Form', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => (string) $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + 'landing_page' => '', + 'tag' => '', + ], + ], + ] + ); + + // Navigate to the page and wait for the notice to appear. + $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); + $I->waitForElementVisible($this->noticeSelector); + + // Assert the notice displays the legacy form by name. + $I->see('Form: ' . $_ENV['CONVERTKIT_API_LEGACY_FORM_NAME'], $this->noticeSelector); + + // Assert the notice does not have a dismiss button. + $I->dontSeeElementInDOM($this->noticeSelector . ' button.components-notice__dismiss'); + } + + /** + * Test that a page with a legacy Landing Page assignment displays the + * warning notice inside Gutenberg, listing the legacy landing page by + * name. + * + * @since 3.3.7 + * + * @param EndToEndTester $I Tester. + */ + public function testNoticeDisplaysForLegacyLandingPage(EndToEndTester $I) + { + // Create a page with a Legacy Landing Page assignment. + $pageID = $I->havePageInDatabase( + [ + 'post_status' => 'publish', + 'post_title' => 'Kit: Legacy Notice: Gutenberg: Landing Page', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => (string) $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_ID'], + 'tag' => '', + ], + ], + ] + ); + + // Navigate to the page and wait for the notice to appear. + $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); + $I->waitForElementVisible($this->noticeSelector); + + // Assert the notice displays the legacy landing page by name. + $I->see('Landing Page: ' . $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_NAME'], $this->noticeSelector); + + // Assert the notice does not have a dismiss button. + $I->dontSeeElementInDOM($this->noticeSelector . ' button.components-notice__dismiss'); + } + + /** + * Test that a page with a Restrict Content assignment referencing a + * legacy Kit form displays the warning notice inside Gutenberg, scoped + * to Member Content. + * + * @since 3.3.7 + * + * @param EndToEndTester $I Tester. + */ + public function testNoticeDisplaysForLegacyRestrictContentForm(EndToEndTester $I) + { + // Create a page with a Restrict Content assignment referencing a + // legacy Kit form. + $pageID = $I->havePageInDatabase( + [ + 'post_status' => 'publish', + 'post_title' => 'Kit: Legacy Notice: Gutenberg: Restrict Content', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => '', + 'tag' => '', + 'restrict_content' => 'form_' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + ], + ], + ] + ); + + // Navigate to the page and wait for the notice to appear. + $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); + $I->waitForElementVisible($this->noticeSelector); + + // Assert the notice displays the legacy form by name. + $I->see('Member Content: ' . $_ENV['CONVERTKIT_API_LEGACY_FORM_NAME'], $this->noticeSelector); + + // Assert the notice does not have a dismiss button. + $I->dontSeeElementInDOM($this->noticeSelector . ' button.components-notice__dismiss'); + } + + /** + * Test that a page assigning only v4 Kit resources does not display + * the legacy warning notice inside Gutenberg. + * + * @since 3.3.7 + * + * @param EndToEndTester $I Tester. + */ + public function testNoticeDoesNotDisplayForV4Resources(EndToEndTester $I) + { + // Create a page assigning only v4 Kit resources. + $pageID = $I->havePageInDatabase( + [ + 'post_status' => 'publish', + 'post_title' => 'Kit: Legacy Notice: Gutenberg: V4 Only', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => (string) $_ENV['CONVERTKIT_API_FORM_ID'], + 'landing_page' => (string) $_ENV['CONVERTKIT_API_LANDING_PAGE_ID'], + 'tag' => '', + ], + ], + ] + ); + + // Navigate to the page and wait for Gutenberg to settle. + $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); + + // Assert the notice is not present. + $I->waitForElementVisible('.block-editor'); + $I->dontSeeElementInDOM($this->noticeSelector); + } + + /** + * Test that a page with legacy form + legacy landing page + legacy + * Member Content assignments displays a single consolidated warning + * notice inside Gutenberg with all three items listed. + * + * @since 3.3.7 + * + * @param EndToEndTester $I Tester. + */ + public function testConsolidatedNoticeWhenMultipleLegacy(EndToEndTester $I) + { + // Create a page with legacy form + legacy landing page + legacy + // Member Content assignments. + $pageID = $I->havePageInDatabase( + [ + 'post_status' => 'publish', + 'post_title' => 'Kit: Legacy Notice: Gutenberg: Consolidated', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => (string) $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + 'landing_page' => (string) $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_ID'], + 'tag' => '', + 'restrict_content' => 'form_' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + ], + ], + ] + ); + + // Navigate to the page and wait for the notice to appear. + $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); + $I->waitForElementVisible($this->noticeSelector); + + // Assert the notice displays all three legacy resources. + $I->seeNumberOfElementsInDOM($this->noticeSelector, 1); + $I->see('Form: ' . $_ENV['CONVERTKIT_API_LEGACY_FORM_NAME'], $this->noticeSelector); + $I->see('Landing Page: ' . $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_NAME'], $this->noticeSelector); + $I->see('Member Content: ' . $_ENV['CONVERTKIT_API_LEGACY_FORM_NAME'], $this->noticeSelector); + } + + /** + * 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.7 + * + * @param EndToEndTester $I Tester. + */ + public function _passed(EndToEndTester $I) + { + $I->deactivateKitPlugin($I); + $I->resetKitPlugin($I); + } +} diff --git a/tests/EndToEnd/general/other/ClassicEditorLegacyNoticeCest.php b/tests/EndToEnd/general/other/ClassicEditorLegacyNoticeCest.php new file mode 100644 index 000000000..91040f2bf --- /dev/null +++ b/tests/EndToEnd/general/other/ClassicEditorLegacyNoticeCest.php @@ -0,0 +1,239 @@ +activateKitPlugin($I); + $I->activateThirdPartyPlugin($I, 'classic-editor'); + $I->setupKitPlugin($I); + $I->setupKitPluginResources($I); + } + + /** + * Test that a page with a legacy Form assignment displays the warning + * notice on the classic editor, listing the legacy form by name. + * + * @since 3.3.7 + * + * @param EndToEndTester $I Tester. + */ + public function testNoticeDisplaysForLegacyForm(EndToEndTester $I) + { + // Create a page with a Legacy Form assignment. + $pageID = $I->havePageInDatabase( + [ + 'post_status' => 'publish', + 'post_title' => 'Kit: Legacy Notice: Form', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => (string) $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + 'landing_page' => '', + 'tag' => '', + ], + ], + ] + ); + + // Navigate to the page and wait for the notice to appear. + $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); + $I->seeElementInDOM($this->noticeSelector); + + // Assert the notice displays the legacy form by name. + $I->see('Form: ' . $_ENV['CONVERTKIT_API_LEGACY_FORM_NAME'], $this->noticeSelector); + + // Assert the notice does not have a dismiss button. + $I->dontSeeElementInDOM($this->noticeSelector . ' button.notice-dismiss'); + } + + /** + * Test that a page with a legacy Landing Page assignment displays the + * warning notice on the classic editor, listing the legacy landing + * page by name. + * + * @since 3.3.7 + * + * @param EndToEndTester $I Tester. + */ + public function testNoticeDisplaysForLegacyLandingPage(EndToEndTester $I) + { + // Create a page with a Legacy Landing Page assignment. + $pageID = $I->havePageInDatabase( + [ + 'post_status' => 'publish', + 'post_title' => 'Kit: Legacy Notice: Landing Page', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => (string) $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_ID'], + 'tag' => '', + ], + ], + ] + ); + + // Navigate to the page and wait for the notice to appear. + $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); + $I->seeElementInDOM($this->noticeSelector); + + // Assert the notice displays the legacy landing page by name. + $I->see('Landing Page: ' . $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_NAME'], $this->noticeSelector); + + // Assert the notice does not have a dismiss button. + $I->dontSeeElementInDOM($this->noticeSelector . ' button.notice-dismiss'); + } + + /** + * Test that a page with a Restrict Content assignment referencing a + * legacy Kit form displays the warning notice on the classic editor, + * scoped to Member Content. + * + * @since 3.3.7 + * + * @param EndToEndTester $I Tester. + */ + public function testNoticeDisplaysForLegacyRestrictContentForm(EndToEndTester $I) + { + // Create a page with a Restrict Content assignment referencing a + // legacy Kit form. + $pageID = $I->havePageInDatabase( + [ + 'post_status' => 'publish', + 'post_title' => 'Kit: Legacy Notice: Restrict Content', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => '', + 'tag' => '', + 'restrict_content' => 'form_' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + ], + ], + ] + ); + + // Navigate to the page and wait for the notice to appear. + $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); + $I->seeElementInDOM($this->noticeSelector); + + // Assert the notice displays the legacy form by name. + $I->see('Member Content: ' . $_ENV['CONVERTKIT_API_LEGACY_FORM_NAME'], $this->noticeSelector); + + // Assert the notice does not have a dismiss button. + $I->dontSeeElementInDOM($this->noticeSelector . ' button.notice-dismiss'); + } + + /** + * Test that a page assigning only v4 Kit resources does not display + * the legacy warning notice. + * + * @since 3.3.7 + * + * @param EndToEndTester $I Tester. + */ + public function testNoticeDoesNotDisplayForV4Resources(EndToEndTester $I) + { + // Create a page assigning only v4 Kit resources. + $pageID = $I->havePageInDatabase( + [ + 'post_status' => 'publish', + 'post_title' => 'Kit: Legacy Notice: V4 Only', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => (string) $_ENV['CONVERTKIT_API_FORM_ID'], + 'landing_page' => (string) $_ENV['CONVERTKIT_API_LANDING_PAGE_ID'], + 'tag' => '', + ], + ], + ] + ); + + // Navigate to the page and wait for the notice to appear. + $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); + + // Assert the notice is not present. + $I->dontSee('Kit: This page uses one or more legacy Kit resources'); + } + + /** + * Test that a page with legacy form + legacy landing page + legacy + * Member Content assignments displays a single consolidated warning + * notice with all three items listed. + * + * @since 3.3.7 + * + * @param EndToEndTester $I Tester. + */ + public function testConsolidatedNoticeWhenMultipleLegacyResources(EndToEndTester $I) + { + // Create a page with legacy form + legacy landing page + legacy + // Member Content assignments. + $pageID = $I->havePageInDatabase( + [ + 'post_status' => 'publish', + 'post_title' => 'Kit: Legacy Notice: Consolidated', + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => (string) $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + 'landing_page' => (string) $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_ID'], + 'tag' => '', + 'restrict_content' => 'form_' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + ], + ], + ] + ); + + // Navigate to the page and wait for the notice to appear. + $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); + + // Assert the notice displays all three legacy resources. + $I->seeNumberOfElementsInDOM($this->noticeSelector, 1); + $I->see('Form: ' . $_ENV['CONVERTKIT_API_LEGACY_FORM_NAME'], $this->noticeSelector); + + // Assert the notice displays the legacy landing page by name. + $I->see('Landing Page: ' . $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_NAME'], $this->noticeSelector); + $I->see('Member Content: ' . $_ENV['CONVERTKIT_API_LEGACY_FORM_NAME'], $this->noticeSelector); + } + + /** + * 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.7 + * + * @param EndToEndTester $I Tester. + */ + public function _passed(EndToEndTester $I) + { + $I->deactivateThirdPartyPlugin($I, 'classic-editor'); + $I->deactivateKitPlugin($I); + $I->resetKitPlugin($I); + } +} diff --git a/tests/Integration/AdminLegacyResourceNoticeTest.php b/tests/Integration/AdminLegacyResourceNoticeTest.php new file mode 100644 index 000000000..26ac6bbe8 --- /dev/null +++ b/tests/Integration/AdminLegacyResourceNoticeTest.php @@ -0,0 +1,228 @@ + $_ENV['CONVERTKIT_OAUTH_ACCESS_TOKEN'], + 'refresh_token' => $_ENV['CONVERTKIT_OAUTH_REFRESH_TOKEN'], + ] + ); + + // Refresh Forms and Landing Pages resources so is_legacy() calls resolve + // correctly against the API-backed cache. + $forms = new \ConvertKit_Resource_Forms(); + $forms->refresh(); + + $landing_pages = new \ConvertKit_Resource_Landing_Pages(); + $landing_pages->refresh(); + + $this->notice = new \ConvertKit_Admin_Legacy_Resource_Notice(); + } + + /** + * Test that a settings array with only v4 resource assignments returns + * no warnings. + * + * @since 3.3.7 + */ + public function testNoWarningsForV4Resources() + { + $warnings = $this->notice->get_legacy_warnings_for_post_settings( + [ + 'form' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'landing_page' => $_ENV['CONVERTKIT_API_LANDING_PAGE_ID'], + 'restrict_content' => 'form_' . $_ENV['CONVERTKIT_API_FORM_ID'], + ] + ); + + $this->assertSame([], $warnings); + } + + /** + * Test that a legacy form assignment produces a form-scoped warning + * containing the form's name. + * + * @since 3.3.7 + */ + public function testWarningForLegacyForm() + { + $warnings = $this->notice->get_legacy_warnings_for_post_settings( + [ + 'form' => $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + ] + ); + + $this->assertCount(1, $warnings); + $this->assertStringContainsString('Form:', $warnings[0]); + $this->assertStringContainsString($_ENV['CONVERTKIT_API_LEGACY_FORM_NAME'], $warnings[0]); + } + + /** + * Test that a legacy landing page assignment (by numeric ID) produces + * a landing-page-scoped warning containing the landing page's name. + * + * @since 3.3.7 + */ + public function testWarningForLegacyLandingPageByID() + { + $warnings = $this->notice->get_legacy_warnings_for_post_settings( + [ + 'landing_page' => $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_ID'], + ] + ); + + $this->assertCount(1, $warnings); + $this->assertStringContainsString('Landing Page:', $warnings[0]); + $this->assertStringContainsString($_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_NAME'], $warnings[0]); + } + + /** + * Test that a legacy landing page assignment stored as a URL string + * (pre-1.9.6 storage) produces a landing-page-scoped warning using the + * fallback label since we can't resolve a name from the URL alone. + * + * @since 3.3.7 + */ + public function testWarningForLegacyLandingPageByURL() + { + $warnings = $this->notice->get_legacy_warnings_for_post_settings( + [ + 'landing_page' => $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_URL'], + ] + ); + + $this->assertCount(1, $warnings); + $this->assertStringContainsString('Landing Page:', $warnings[0]); + $this->assertStringContainsString('a Legacy Landing Page', $warnings[0]); + } + + /** + * Test that a restrict_content value of `form_` produces a + * Member Content warning. + * + * @since 3.3.7 + */ + public function testWarningForLegacyRestrictContentForm() + { + $warnings = $this->notice->get_legacy_warnings_for_post_settings( + [ + 'restrict_content' => 'form_' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + ] + ); + + $this->assertCount(1, $warnings); + $this->assertStringContainsString('Member Content:', $warnings[0]); + $this->assertStringContainsString($_ENV['CONVERTKIT_API_LEGACY_FORM_NAME'], $warnings[0]); + } + + /** + * Test that restrict_content values of `tag_` and `product_` + * never trigger a warning, because tags and products have no legacy + * variant. + * + * @since 3.3.7 + */ + public function testNoWarningForLegacyRestrictContentTagOrProduct() + { + $tag = $this->notice->get_legacy_warnings_for_post_settings( + [ + 'restrict_content' => 'tag_' . $_ENV['CONVERTKIT_API_TAG_ID'], + ] + ); + $product = $this->notice->get_legacy_warnings_for_post_settings( + [ + 'restrict_content' => 'product_1', + ] + ); + + $this->assertSame([], $tag); + $this->assertSame([], $product); + } + + /** + * Test that a settings array with legacy form + legacy landing page + + * legacy restrict_content form produces three warnings in a single + * returned array. + * + * @since 3.3.7 + */ + public function testWarningsForMultipleLegacyResources() + { + $warnings = $this->notice->get_legacy_warnings_for_post_settings( + [ + 'form' => $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + 'landing_page' => $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_ID'], + 'restrict_content' => 'form_' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], + ] + ); + + $this->assertCount(3, $warnings); + } + + /** + * Test that when a legacy form ID doesn't resolve to any cached resource + * (e.g. the form was deleted from the Kit account), the warning falls + * back to "a Legacy Form" rather than omitting the entry. + * + * @since 3.3.7 + */ + public function testWarningWithMissingResourceFallsBackToLabel() + { + // Wipe the Forms cache so is_legacy() can no longer resolve any + // numeric ID to a real resource. is_legacy() will therefore return + // false for numeric IDs — but a URL-based landing page assignment + // still resolves as legacy by the URL-string check, exercising the + // name-fallback path. + delete_option('convertkit_forms'); + delete_option('convertkit_landing_pages'); + + $warnings = $this->notice->get_legacy_warnings_for_post_settings( + [ + 'landing_page' => $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_URL'], + ] + ); + + $this->assertCount(1, $warnings); + $this->assertStringContainsString('a Legacy Landing Page', $warnings[0]); + } +} diff --git a/wp-convertkit.php b/wp-convertkit.php index caead13e1..a400ce5fd 100644 --- a/wp-convertkit.php +++ b/wp-convertkit.php @@ -111,6 +111,7 @@ require_once CONVERTKIT_PLUGIN_PATH . '/admin/class-convertkit-admin-cache-plugins.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/class-convertkit-admin-category.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/class-convertkit-admin-landing-page.php'; +require_once CONVERTKIT_PLUGIN_PATH . '/admin/class-convertkit-admin-legacy-resource-notice.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/class-convertkit-admin-post.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/class-convertkit-admin-refresh-resources.php'; require_once CONVERTKIT_PLUGIN_PATH . '/admin/class-convertkit-admin-restrict-content.php'; From 8a88ebff8694fcce4c10c5bef3980888a77f05ff Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 4 Aug 2026 14:27:29 +0800 Subject: [PATCH 2/5] Lint JS --- resources/backend/js/legacy-resource-notice.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/resources/backend/js/legacy-resource-notice.js b/resources/backend/js/legacy-resource-notice.js index cf605d543..484dfef6b 100644 --- a/resources/backend/js/legacy-resource-notice.js +++ b/resources/backend/js/legacy-resource-notice.js @@ -40,10 +40,8 @@ document.addEventListener('DOMContentLoaded', function () { .join('\n'); // Create the notice. - wp.data - .dispatch('core/notices') - .createWarningNotice(message, { - id: convertkit_legacy_resource_notice.id, - isDismissible: false, - }); + wp.data.dispatch('core/notices').createWarningNotice(message, { + id: convertkit_legacy_resource_notice.id, + isDismissible: false, + }); }); From f09ac1db23e4cd936ae7fb1ed5ecc6819b2aa6ec Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 5 Aug 2026 16:19:37 +0800 Subject: [PATCH 3/5] Fix tests and improve notice wording --- ...class-convertkit-admin-legacy-resource-notice.php | 6 +++--- resources/backend/js/legacy-resource-notice.js | 12 ++++++------ .../general/other/ClassicEditorLegacyNoticeCest.php | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/admin/class-convertkit-admin-legacy-resource-notice.php b/admin/class-convertkit-admin-legacy-resource-notice.php index 259555f0a..27a9f18ca 100644 --- a/admin/class-convertkit-admin-legacy-resource-notice.php +++ b/admin/class-convertkit-admin-legacy-resource-notice.php @@ -71,7 +71,7 @@ public function output_classic_editor_notice() {

: - +

    self::GUTENBERG_NOTICE_ID, - 'intro' => __( 'Kit: This page uses one or more legacy Kit resources that are no longer offered for new selections. The following continue to work, but we recommend migrating:', 'convertkit' ), + 'intro' => __( 'Kit: The Kit settings for this post reference legacy resources. They still work, but should be migrated:', 'convertkit' ), 'warnings' => $warnings, ) ); @@ -205,7 +205,7 @@ private function get_legacy_warnings_for_post_settings( $post_id ) { */ private function get_form_display_name( $form_id, $forms ) { - $form = $forms->get_by_id( $form_id ); + $form = $forms->get_by_id( (int) $form_id ); if ( $form && ! empty( $form['name'] ) ) { return sanitize_text_field( $form['name'] ); } diff --git a/resources/backend/js/legacy-resource-notice.js b/resources/backend/js/legacy-resource-notice.js index 484dfef6b..e891714e2 100644 --- a/resources/backend/js/legacy-resource-notice.js +++ b/resources/backend/js/legacy-resource-notice.js @@ -30,14 +30,14 @@ document.addEventListener('DOMContentLoaded', function () { } // Build the notice. + // Gutenberg's Notice component renders `message` as plain text with + // `white-space: normal`, so `\n` collapses to a space and any leading + // `- ` dashes look odd. Inline the items with `; ` separators instead — + // this reads naturally on the single line the component actually renders. const message = convertkit_legacy_resource_notice.intro + - '\n' + - convertkit_legacy_resource_notice.warnings - .map(function (warning) { - return '- ' + warning; - }) - .join('\n'); + ' ' + + convertkit_legacy_resource_notice.warnings.join('; '); // Create the notice. wp.data.dispatch('core/notices').createWarningNotice(message, { diff --git a/tests/EndToEnd/general/other/ClassicEditorLegacyNoticeCest.php b/tests/EndToEnd/general/other/ClassicEditorLegacyNoticeCest.php index 91040f2bf..85195a09e 100644 --- a/tests/EndToEnd/general/other/ClassicEditorLegacyNoticeCest.php +++ b/tests/EndToEnd/general/other/ClassicEditorLegacyNoticeCest.php @@ -178,7 +178,7 @@ public function testNoticeDoesNotDisplayForV4Resources(EndToEndTester $I) $I->amOnAdminPage('post.php?post=' . $pageID . '&action=edit'); // Assert the notice is not present. - $I->dontSee('Kit: This page uses one or more legacy Kit resources'); + $I->dontSee('The Kit settings for this post reference legacy resources'); } /** From 4ff05d53368e2ae18d13cdd3beb1b6ed3d7ae7f9 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 5 Aug 2026 21:19:00 +0800 Subject: [PATCH 4/5] Updated tests --- ...onvertkit-admin-legacy-resource-notice.php | 48 ++++++++++++++----- .../AdminLegacyResourceNoticeTest.php | 18 +++---- 2 files changed, 44 insertions(+), 22 deletions(-) diff --git a/admin/class-convertkit-admin-legacy-resource-notice.php b/admin/class-convertkit-admin-legacy-resource-notice.php index 27a9f18ca..194fb0962 100644 --- a/admin/class-convertkit-admin-legacy-resource-notice.php +++ b/admin/class-convertkit-admin-legacy-resource-notice.php @@ -151,9 +151,26 @@ public function enqueue_gutenberg_notice() { */ private function get_legacy_warnings_for_post_settings( $post_id ) { - // Get Post settings. $convertkit_post = new ConvertKit_Post( $post_id ); - $settings = $convertkit_post->get(); + + return $this->get_legacy_warnings_for_settings( $convertkit_post->get() ); + + } + + /** + * Returns an array of warning strings for the given Kit settings array, + * if the settings reference a Legacy Form or Legacy Landing Page. + * + * Public so it can be exercised directly from integration tests without + * having to create a real Post; the hook handlers use the private + * post-ID wrapper above. + * + * @since 3.3.7 + * + * @param array $settings Kit settings array (form, landing_page, restrict_content). + * @return array + */ + public function get_legacy_warnings_for_settings( $settings ) { // Get resources. $forms = new ConvertKit_Resource_Forms(); @@ -163,30 +180,35 @@ private function get_legacy_warnings_for_post_settings( $post_id ) { $warnings = array(); // Form. - if ( $forms->is_legacy( $convertkit_post->get_form() ) ) { + if ( ! empty( $settings['form'] ) && $forms->is_legacy( $settings['form'] ) ) { $warnings[] = sprintf( /* translators: %s: Form name */ __( 'Form: %s', 'convertkit' ), - $this->get_form_display_name( $convertkit_post->get_form(), $forms ) + $this->get_form_display_name( $settings['form'], $forms ) ); } // Landing Page. - if ( $landing_pages->is_legacy( $convertkit_post->get_landing_page() ) ) { + if ( ! empty( $settings['landing_page'] ) && $landing_pages->is_legacy( $settings['landing_page'] ) ) { $warnings[] = sprintf( /* translators: %s: Landing page name */ __( 'Landing Page: %s', 'convertkit' ), - $this->get_landing_page_display_name( $convertkit_post->get_landing_page(), $landing_pages ) + $this->get_landing_page_display_name( $settings['landing_page'], $landing_pages ) ); } - // Restrict Content. - if ( $convertkit_post->get_restrict_content_type() === 'form' && $forms->is_legacy( $convertkit_post->get_restrict_content_id() ) ) { - $warnings[] = sprintf( - /* translators: %s: Form name */ - __( 'Member Content: %s', 'convertkit' ), - $this->get_form_display_name( $convertkit_post->get_restrict_content_id(), $forms ) - ); + // Restrict Content. Value shape is `_` — e.g. `form_123`, + // `tag_456`, `product_789`. Only form types have a legacy variant. + if ( ! empty( $settings['restrict_content'] ) && strpos( (string) $settings['restrict_content'], '_' ) !== false ) { + list( $type, $id ) = explode( '_', (string) $settings['restrict_content'], 2 ); + + if ( 'form' === $type && $forms->is_legacy( $id ) ) { + $warnings[] = sprintf( + /* translators: %s: Form name */ + __( 'Member Content: %s', 'convertkit' ), + $this->get_form_display_name( $id, $forms ) + ); + } } return $warnings; diff --git a/tests/Integration/AdminLegacyResourceNoticeTest.php b/tests/Integration/AdminLegacyResourceNoticeTest.php index 26ac6bbe8..6b1240b04 100644 --- a/tests/Integration/AdminLegacyResourceNoticeTest.php +++ b/tests/Integration/AdminLegacyResourceNoticeTest.php @@ -67,7 +67,7 @@ public function setUp(): void */ public function testNoWarningsForV4Resources() { - $warnings = $this->notice->get_legacy_warnings_for_post_settings( + $warnings = $this->notice->get_legacy_warnings_for_settings( [ 'form' => $_ENV['CONVERTKIT_API_FORM_ID'], 'landing_page' => $_ENV['CONVERTKIT_API_LANDING_PAGE_ID'], @@ -86,7 +86,7 @@ public function testNoWarningsForV4Resources() */ public function testWarningForLegacyForm() { - $warnings = $this->notice->get_legacy_warnings_for_post_settings( + $warnings = $this->notice->get_legacy_warnings_for_settings( [ 'form' => $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], ] @@ -105,7 +105,7 @@ public function testWarningForLegacyForm() */ public function testWarningForLegacyLandingPageByID() { - $warnings = $this->notice->get_legacy_warnings_for_post_settings( + $warnings = $this->notice->get_legacy_warnings_for_settings( [ 'landing_page' => $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_ID'], ] @@ -125,7 +125,7 @@ public function testWarningForLegacyLandingPageByID() */ public function testWarningForLegacyLandingPageByURL() { - $warnings = $this->notice->get_legacy_warnings_for_post_settings( + $warnings = $this->notice->get_legacy_warnings_for_settings( [ 'landing_page' => $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_URL'], ] @@ -144,7 +144,7 @@ public function testWarningForLegacyLandingPageByURL() */ public function testWarningForLegacyRestrictContentForm() { - $warnings = $this->notice->get_legacy_warnings_for_post_settings( + $warnings = $this->notice->get_legacy_warnings_for_settings( [ 'restrict_content' => 'form_' . $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], ] @@ -164,12 +164,12 @@ public function testWarningForLegacyRestrictContentForm() */ public function testNoWarningForLegacyRestrictContentTagOrProduct() { - $tag = $this->notice->get_legacy_warnings_for_post_settings( + $tag = $this->notice->get_legacy_warnings_for_settings( [ 'restrict_content' => 'tag_' . $_ENV['CONVERTKIT_API_TAG_ID'], ] ); - $product = $this->notice->get_legacy_warnings_for_post_settings( + $product = $this->notice->get_legacy_warnings_for_settings( [ 'restrict_content' => 'product_1', ] @@ -188,7 +188,7 @@ public function testNoWarningForLegacyRestrictContentTagOrProduct() */ public function testWarningsForMultipleLegacyResources() { - $warnings = $this->notice->get_legacy_warnings_for_post_settings( + $warnings = $this->notice->get_legacy_warnings_for_settings( [ 'form' => $_ENV['CONVERTKIT_API_LEGACY_FORM_ID'], 'landing_page' => $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_ID'], @@ -216,7 +216,7 @@ public function testWarningWithMissingResourceFallsBackToLabel() delete_option('convertkit_forms'); delete_option('convertkit_landing_pages'); - $warnings = $this->notice->get_legacy_warnings_for_post_settings( + $warnings = $this->notice->get_legacy_warnings_for_settings( [ 'landing_page' => $_ENV['CONVERTKIT_API_LEGACY_LANDING_PAGE_URL'], ] From f0ec3125af503324e79a616cfacfc6a1d61dc237 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Wed, 5 Aug 2026 21:26:51 +0800 Subject: [PATCH 5/5] PHPStan compat. --- admin/class-convertkit-admin-legacy-resource-notice.php | 1 + 1 file changed, 1 insertion(+) diff --git a/admin/class-convertkit-admin-legacy-resource-notice.php b/admin/class-convertkit-admin-legacy-resource-notice.php index 194fb0962..547ca151c 100644 --- a/admin/class-convertkit-admin-legacy-resource-notice.php +++ b/admin/class-convertkit-admin-legacy-resource-notice.php @@ -201,6 +201,7 @@ public function get_legacy_warnings_for_settings( $settings ) { // `tag_456`, `product_789`. Only form types have a legacy variant. if ( ! empty( $settings['restrict_content'] ) && strpos( (string) $settings['restrict_content'], '_' ) !== false ) { list( $type, $id ) = explode( '_', (string) $settings['restrict_content'], 2 ); + $id = (int) $id; if ( 'form' === $type && $forms->is_legacy( $id ) ) { $warnings[] = sprintf(