diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d59ffb260..c59a882fb 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -33,7 +33,7 @@ jobs: WORDPRESS_DB_SQL_DUMP_FILE: tests/Support/Data/dump.sql # Used to populate the test site database for WordPress 6.2.8 INSTALL_PLUGINS: "admin-menu-editor autoptimize beaver-builder-lite-version block-visibility contact-form-7 classic-editor custom-post-type-ui debloat elementor forminator jetpack-boost mailchimp-for-wp rocket-lazy-load woocommerce wordpress-seo wpforms-lite litespeed-cache wp-crontrol wp-super-cache w3-total-cache wp-fastest-cache wp-optimize sg-cachepress" # Don't include this repository's Plugin here. INSTALL_PLUGINS_URLS: "https://downloads.wordpress.org/plugin/convertkit-for-woocommerce.1.6.4.zip http://cktestplugins.wpengine.com/wp-content/uploads/2024/01/convertkit-action-filter-tests.zip http://cktestplugins.wpengine.com/wp-content/uploads/2024/11/disable-doing-it-wrong-notices.zip http://cktestplugins.wpengine.com/wp-content/uploads/2025/03/uncode-js_composer.7.8.zip http://cktestplugins.wpengine.com/wp-content/uploads/2025/03/uncode-core.zip" # URLs to specific third party Plugins - INSTALL_THEMES_URLS: "http://cktestplugins.wpengine.com/wp-content/uploads/2025/03/uncode.zip http://cktestplugins.wpengine.com/wp-content/uploads/2025/04/Divi.zip" + INSTALL_THEMES_URLS: "http://cktestplugins.wpengine.com/wp-content/uploads/2025/03/uncode.zip http://cktestplugins.wpengine.com/wp-content/uploads/2025/04/Divi.zip http://cktestplugins.wpengine.com/wp-content/uploads/2026/01/impeka.zip" CONVERTKIT_API_KEY: ${{ secrets.CONVERTKIT_API_KEY }} # ConvertKit API Key, stored in the repository's Settings > Secrets CONVERTKIT_API_SECRET: ${{ secrets.CONVERTKIT_API_SECRET }} # ConvertKit API Secret, stored in the repository's Settings > Secrets CONVERTKIT_API_KEY_NO_DATA: ${{ secrets.CONVERTKIT_API_KEY_NO_DATA }} # ConvertKit API Key for ConvertKit account with no data, stored in the repository's Settings > Secrets diff --git a/admin/section/class-convertkit-admin-section-restrict-content.php b/admin/section/class-convertkit-admin-section-restrict-content.php index 6f6ba2512..7107876bf 100644 --- a/admin/section/class-convertkit-admin-section-restrict-content.php +++ b/admin/section/class-convertkit-admin-section-restrict-content.php @@ -333,6 +333,21 @@ public function register_fields() { ) ); + add_settings_field( + 'container_css_classes', + __( 'Container CSS Classes', 'convertkit' ), + array( $this, 'text_callback' ), + $this->settings_key, + $this->name, + array( + 'name' => 'container_css_classes', + 'label_for' => 'container_css_classes', + 'description' => array( + __( 'Optional CSS classes to apply to the container wrapping the content preview and call to action for non-authenticated subscribers.', 'convertkit' ), + ), + ) + ); + } /** diff --git a/includes/class-convertkit-output-restrict-content.php b/includes/class-convertkit-output-restrict-content.php index d07fcfdc9..17e97b822 100644 --- a/includes/class-convertkit-output-restrict-content.php +++ b/includes/class-convertkit-output-restrict-content.php @@ -1226,6 +1226,27 @@ private function restrict_content( $content ) { */ $call_to_action = apply_filters( 'convertkit_output_restrict_content_call_to_action', $call_to_action, $this->post_id ); + // Fetch container CSS classes. + $container_css_classes = explode( ' ', $this->restrict_content_settings->get_by_key( 'container_css_classes' ) ); + + /** + * Define the container CSS classes to wrap the content preview and call to action within. + * + * @since 3.1.4 + * + * @param array $container_css_classes Container CSS classes. + * @param int $post_id Post ID. + */ + $container_css_classes = apply_filters( 'convertkit_output_restrict_content_container_css_classes', $container_css_classes, $this->post_id ); + + // Remove empty CSS classes. + $container_css_classes = array_filter( $container_css_classes ); + + // If container CSS classes are set, return the content preview and call to action wrapped in the container. + if ( count( $container_css_classes ) ) { + return '
' . $content_preview . $call_to_action . '
'; + } + // Return the content preview and its call to action. return $content_preview . $call_to_action; diff --git a/includes/class-convertkit-settings-restrict-content.php b/includes/class-convertkit-settings-restrict-content.php index e061b2dc6..6653c18df 100644 --- a/includes/class-convertkit-settings-restrict-content.php +++ b/includes/class-convertkit-settings-restrict-content.php @@ -151,6 +151,7 @@ public function get_defaults() { 'email_description_text' => __( 'We\'ll email you a magic code to log you in without a password.', 'convertkit' ), 'email_check_heading' => __( 'We just emailed you a log in code', 'convertkit' ), 'email_check_text' => __( 'Enter the code below to finish logging in', 'convertkit' ), + 'container_css_classes' => '', ); /** diff --git a/includes/functions.php b/includes/functions.php index 038248f23..6ad62e1e0 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -641,6 +641,32 @@ function convertkit_output_intercom_messenger() { } +/** + * Checks if the given Theme is active. + * + * @since 3.1.4 + * + * @param string $theme_name Theme name. + * @return bool + */ +function convertkit_is_theme_active( $theme_name ) { + + // Assume Theme isn't active if we can't detect it. + if ( ! function_exists( 'wp_get_theme' ) ) { + return false; + } + + // Check the Parent Theme if we're on a Child Theme. + if ( wp_get_theme()->parent() ) { + $theme = wp_get_theme()->parent(); + } else { + $theme = wp_get_theme(); + } + + return strtolower( $theme->get( 'Name' ) ) === strtolower( $theme_name ); + +} + /** * Returns permitted HTML output when using wp_kses( ..., convertkit_kses_allowed_html()). * diff --git a/includes/integrations/class-convertkit-impeka.php b/includes/integrations/class-convertkit-impeka.php new file mode 100644 index 000000000..42432c16c --- /dev/null +++ b/includes/integrations/class-convertkit-impeka.php @@ -0,0 +1,50 @@ +testRestrictContentDisplaysContent($I); } + /** + * Test that restricting content by a Form specified in the Page Settings works when + * creating and viewing a new WordPress Page, and that the container CSS classes are applied + * to the content preview and call to action. + * + * @since 3.1.4 + * + * @param EndToEndTester $I Tester. + */ + public function testRestrictContentContainerCSSClasses(EndToEndTester $I) + { + // Setup Kit Plugin, disabling JS. + $I->setupKitPluginDisableJS($I); + $I->setupKitPluginResources($I); + + // Define Restrict Content settings. + $settings = [ + 'container_css_classes' => 'custom-container-css-class', + ]; + + // Setup Restrict Content functionality with container CSS classes. + $I->setupKitPluginRestrictContent($I, $settings); + + // Add a Page using the Gutenberg editor. + $I->addGutenbergPage( + $I, + title: 'Kit: Page: Restrict Content: Form' + ); + + // Configure metabox's Restrict Content setting = Form name. + $I->configureMetaboxSettings( + $I, + 'wp-convertkit-meta-box', + [ + 'form' => [ 'select2', 'None' ], + 'restrict_content' => [ 'select2', $_ENV['CONVERTKIT_API_FORM_NAME'] ], + ] + ); + + // Add blocks. + $I->addGutenbergParagraphBlock($I, 'Visible content.'); + $I->addGutenbergBlock( + $I, + blockName: 'More', + blockProgrammaticName: 'more' + ); + $I->addGutenbergParagraphBlock($I, 'Member-only content.'); + + // Publish Page. + $url = $I->publishGutenbergPage($I); + + // Test Restrict Content functionality. + $I->testRestrictedContentByFormOnFrontend( + $I, + urlOrPageID: $url, + formID: $_ENV['CONVERTKIT_API_FORM_ID'], + options: [ + 'settings' => $settings, + ] + ); + } + /** * Test that restricting content by a Form specified in the Page Settings works when * using the Quick Edit functionality. diff --git a/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php b/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php index dd9813ec7..5ef30d890 100644 --- a/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php +++ b/tests/EndToEnd/restrict-content/general/RestrictContentSettingsCest.php @@ -167,6 +167,7 @@ public function testSaveSettings(EndToEndTester $I) 'email_description_text' => 'Email Description Text', 'email_check_heading' => 'Email Check Heading', 'email_check_text' => 'Email Check Text', + 'container_css_classes' => 'customer-container-class', ); // Save settings. diff --git a/tests/EndToEnd/restrict-content/general/RestrictContentTagCest.php b/tests/EndToEnd/restrict-content/general/RestrictContentTagCest.php index 906e68dfa..2762a9e56 100644 --- a/tests/EndToEnd/restrict-content/general/RestrictContentTagCest.php +++ b/tests/EndToEnd/restrict-content/general/RestrictContentTagCest.php @@ -304,6 +304,68 @@ public function testRestrictContentByInvalidTag(EndToEndTester $I) $I->testRestrictContentDisplaysContent($I); } + /** + * Test that restricting content by a Tag specified in the Page Settings works when + * creating and viewing a new WordPress Page, and that the container CSS classes are applied + * to the content preview and call to action. + * + * @since 3.1.4 + * + * @param EndToEndTester $I Tester. + */ + public function testRestrictContentContainerCSSClasses(EndToEndTester $I) + { + // Setup Kit Plugin, disabling JS. + $I->setupKitPluginDisableJS($I); + $I->setupKitPluginResources($I); + + // Define Restrict Content settings. + $settings = [ + 'container_css_classes' => 'custom-container-css-class', + ]; + + // Setup Restrict Content functionality with container CSS classes. + $I->setupKitPluginRestrictContent($I, $settings); + + // Add a Page using the Gutenberg editor. + $I->addGutenbergPage( + $I, + title: 'Kit: Page: Restrict Content: Tag: Container CSS Classes' + ); + + // Configure metabox's Restrict Content setting = Tag name. + $I->configureMetaboxSettings( + $I, + 'wp-convertkit-meta-box', + [ + 'form' => [ 'select2', 'None' ], + 'restrict_content' => [ 'select2', $_ENV['CONVERTKIT_API_TAG_NAME'] ], + ] + ); + + // Add blocks. + $I->addGutenbergParagraphBlock($I, 'Visible content.'); + $I->addGutenbergBlock( + $I, + blockName: 'More', + blockProgrammaticName: 'more' + ); + $I->addGutenbergParagraphBlock($I, 'Member-only content.'); + + // Publish Page. + $url = $I->publishGutenbergPage($I); + + // Test Restrict Content functionality. + $I->testRestrictedContentByTagOnFrontend( + $I, + urlOrPageID: $url, + emailAddress: $I->generateEmailAddress(), + options: [ + 'settings' => $settings, + ] + ); + } + /** * Test that restricting content by a Tag specified in the Page Settings works when * creating and viewing a new WordPress Page, with Google's reCAPTCHA enabled. diff --git a/tests/EndToEnd/restrict-content/post-types/RestrictContentProductCPTCest.php b/tests/EndToEnd/restrict-content/post-types/RestrictContentProductCPTCest.php index 0ff27b9f8..a630481f3 100644 --- a/tests/EndToEnd/restrict-content/post-types/RestrictContentProductCPTCest.php +++ b/tests/EndToEnd/restrict-content/post-types/RestrictContentProductCPTCest.php @@ -289,6 +289,68 @@ public function testRestrictContentModalByProduct(EndToEndTester $I) $I->testRestrictedContentModal($I, $url); } + /** + * Test that restricting content by a Product specified in the CPT Settings works when + * creating and viewing a new WordPress CPT, and that the container CSS classes are applied + * to the content preview and call to action. + * + * @since 3.1.4 + * + * @param EndToEndTester $I Tester. + */ + public function testRestrictContentContainerCSSClasses(EndToEndTester $I) + { + // Setup Kit Plugin, disabling JS. + $I->setupKitPluginDisableJS($I); + $I->setupKitPluginResources($I); + + // Define Restrict Content settings. + $settings = [ + 'container_css_classes' => 'custom-container-css-class', + ]; + + // Setup Restrict Content functionality with container CSS classes. + $I->setupKitPluginRestrictContent($I, $settings); + + // Add the CPT using the Gutenberg editor. + $I->addGutenbergPage( + $I, + postType: 'article', + title: 'Kit: Article: Restrict Content: Container CSS Classes' + ); + + // Configure metabox's Restrict Content setting = Product name. + $I->configureMetaboxSettings( + $I, + metabox: 'wp-convertkit-meta-box', + configuration: [ + 'form' => [ 'select2', 'None' ], + 'restrict_content' => [ 'select2', $_ENV['CONVERTKIT_API_PRODUCT_NAME'] ], + ] + ); + + // Add blocks. + $I->addGutenbergParagraphBlock($I, 'Visible content.'); + $I->addGutenbergBlock( + $I, + blockName: 'More', + blockProgrammaticName: 'more' + ); + $I->addGutenbergParagraphBlock($I, 'Member-only content.'); + + // Publish Page. + $url = $I->publishGutenbergPage($I); + + // Test Restrict Content functionality. + $I->testRestrictedContentByProductOnFrontend( + $I, + urlOrPageID: $url, + options: [ + 'settings' => $settings, + ] + ); + } + /** * Test that restricting content by a Product that does not exist does not output * a fatal error and instead displays all of the CPT's content. diff --git a/tests/EndToEnd/restrict-content/post-types/RestrictContentProductPageCest.php b/tests/EndToEnd/restrict-content/post-types/RestrictContentProductPageCest.php index 8a80cd146..9f14baba4 100644 --- a/tests/EndToEnd/restrict-content/post-types/RestrictContentProductPageCest.php +++ b/tests/EndToEnd/restrict-content/post-types/RestrictContentProductPageCest.php @@ -254,6 +254,67 @@ public function testRestrictContentModalByProduct(EndToEndTester $I) $I->testRestrictedContentModal($I, $url); } + /** + * Test that restricting content by a Product specified in the Page Settings works when + * creating and viewing a new WordPress Page, and that the container CSS classes are applied + * to the content preview and call to action. + * + * @since 3.1.4 + * + * @param EndToEndTester $I Tester. + */ + public function testRestrictContentContainerCSSClasses(EndToEndTester $I) + { + // Setup Kit Plugin, disabling JS. + $I->setupKitPluginDisableJS($I); + $I->setupKitPluginResources($I); + + // Define Restrict Content settings. + $settings = [ + 'container_css_classes' => 'custom-container-css-class', + ]; + + // Setup Restrict Content functionality with container CSS classes. + $I->setupKitPluginRestrictContent($I, $settings); + + // Add the Page using the Gutenberg editor. + $I->addGutenbergPage( + $I, + title: 'Kit: Page: Restrict Content: Container CSS Classes' + ); + + // Configure metabox's Restrict Content setting = Product name. + $I->configureMetaboxSettings( + $I, + metabox: 'wp-convertkit-meta-box', + configuration: [ + 'form' => [ 'select2', 'None' ], + 'restrict_content' => [ 'select2', $_ENV['CONVERTKIT_API_PRODUCT_NAME'] ], + ] + ); + + // Add blocks. + $I->addGutenbergParagraphBlock($I, 'Visible content.'); + $I->addGutenbergBlock( + $I, + blockName: 'More', + blockProgrammaticName: 'more' + ); + $I->addGutenbergParagraphBlock($I, 'Member-only content.'); + + // Publish Page. + $url = $I->publishGutenbergPage($I); + + // Test Restrict Content functionality. + $I->testRestrictedContentByProductOnFrontend( + $I, + urlOrPageID: $url, + options: [ + 'settings' => $settings, + ] + ); + } + /** * Test that restricting content by a Product specified in the Page Settings displays * an inline error message when the subscriber logged in to view content gated diff --git a/tests/EndToEnd/restrict-content/post-types/RestrictContentProductPostCest.php b/tests/EndToEnd/restrict-content/post-types/RestrictContentProductPostCest.php index ddd1a65e8..786f8d3bf 100644 --- a/tests/EndToEnd/restrict-content/post-types/RestrictContentProductPostCest.php +++ b/tests/EndToEnd/restrict-content/post-types/RestrictContentProductPostCest.php @@ -327,6 +327,68 @@ public function testRestrictContentModalByProduct(EndToEndTester $I) $I->testRestrictedContentModal($I, $url); } + /** + * Test that restricting content by a Product specified in the Post Settings works when + * creating and viewing a new WordPress Post, and that the container CSS classes are applied + * to the content preview and call to action. + * + * @since 3.1.4 + * + * @param EndToEndTester $I Tester. + */ + public function testRestrictContentContainerCSSClasses(EndToEndTester $I) + { + // Setup Kit Plugin, disabling JS. + $I->setupKitPluginDisableJS($I); + $I->setupKitPluginResources($I); + + // Define Restrict Content settings. + $settings = [ + 'container_css_classes' => 'custom-container-css-class', + ]; + + // Setup Restrict Content functionality with container CSS classes. + $I->setupKitPluginRestrictContent($I, $settings); + + // Add the Post using the Gutenberg editor. + $I->addGutenbergPage( + $I, + postType: 'post', + title: 'Kit: Post: Restrict Content: Container CSS Classes' + ); + + // Configure metabox's Restrict Content setting = Product name. + $I->configureMetaboxSettings( + $I, + metabox: 'wp-convertkit-meta-box', + configuration: [ + 'form' => [ 'select2', 'None' ], + 'restrict_content' => [ 'select2', $_ENV['CONVERTKIT_API_PRODUCT_NAME'] ], + ] + ); + + // Add blocks. + $I->addGutenbergParagraphBlock($I, 'Visible content.'); + $I->addGutenbergBlock( + $I, + blockName: 'More', + blockProgrammaticName: 'more' + ); + $I->addGutenbergParagraphBlock($I, 'Member-only content.'); + + // Publish Page. + $url = $I->publishGutenbergPage($I); + + // Test Restrict Content functionality. + $I->testRestrictedContentByProductOnFrontend( + $I, + urlOrPageID: $url, + options: [ + 'settings' => $settings, + ] + ); + } + /** * Test that restricting content by a Product that does not exist does not output * a fatal error and instead displays all of the Post's content. diff --git a/tests/EndToEnd/restrict-content/post-types/RestrictContentProductThirdPartyThemeOrPageBuilderCest.php b/tests/EndToEnd/restrict-content/post-types/RestrictContentProductThirdPartyThemeOrPageBuilderCest.php index 82d82d808..800cc3454 100644 --- a/tests/EndToEnd/restrict-content/post-types/RestrictContentProductThirdPartyThemeOrPageBuilderCest.php +++ b/tests/EndToEnd/restrict-content/post-types/RestrictContentProductThirdPartyThemeOrPageBuilderCest.php @@ -31,6 +31,55 @@ public function _before(EndToEndTester $I) $I->setupKitPluginResources($I); } + /** + * Test that restricting content by a Product specified in the Page Settings works when + * creating and viewing a new WordPress Page using the Impeka theme automatically + * adds Impeka's grve-container CSS class to the Restrict Content container, + * ensuring correct layout. + * + * @since 3.1.4 + * + * @param EndToEndTester $I Tester. + */ + public function testRestrictContentByProductWithImpekaTheme(EndToEndTester $I) + { + // Activate theme. + $I->useTheme('impeka'); + + // Programmatically create a Page using the Visual Composer Page Builder. + $pageID = $I->havePostInDatabase( + [ + 'post_type' => 'page', + 'post_title' => 'Kit: Page: Restrict Content: Product: Impeka Theme with Visual Composer', + 'post_content' => 'Member-only content.', + + // Don't display a Form on this Page, so we test against Restrict Content's Form. + 'meta_input' => [ + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => '', + 'tag' => '', + 'restrict_content' => 'product_' . $_ENV['CONVERTKIT_API_PRODUCT_ID'], + ], + ], + ] + ); + + // Test Restrict Content functionality. + $I->testRestrictedContentByProductOnFrontend( + $I, + urlOrPageID: $pageID, + options: [ + 'visible_content' => '', + 'member_content' => 'Member-only content.', + 'settings' => [ + // Test that the grve-container CSS class is added to the Restrict Content container. + 'container_css_classes' => 'grve-container', + ], + ], + ); + } + /** * Test that restricting content by a Product specified in the Page Settings works when * creating and viewing a new WordPress Page using the Uncode theme both using diff --git a/tests/Integration/ThemeDetectionTest.php b/tests/Integration/ThemeDetectionTest.php new file mode 100644 index 000000000..cf0f50613 --- /dev/null +++ b/tests/Integration/ThemeDetectionTest.php @@ -0,0 +1,65 @@ +assertTrue(convertkit_is_theme_active( 'Twenty Twenty-Five' )); + $this->assertFalse(convertkit_is_theme_active( 'Impeka' )); + + // Switch Theme. + switch_theme('impeka'); + $this->assertFalse(convertkit_is_theme_active( 'Twenty Twenty-Five' )); + $this->assertTrue(convertkit_is_theme_active( 'Impeka' )); + } +} diff --git a/tests/Support/Helper/KitRestrictContent.php b/tests/Support/Helper/KitRestrictContent.php index dc95c6010..f92f00ba9 100644 --- a/tests/Support/Helper/KitRestrictContent.php +++ b/tests/Support/Helper/KitRestrictContent.php @@ -637,6 +637,11 @@ public function testRestrictContentByProductHidesContentWithCTA($I, $options = f } $I->dontSee($options['member_content']); + // If a container CSS classes is set, confirm that the container is displayed. + if ( ! empty($options['settings']['container_css_classes'])) { + $I->seeInSource('
'); + } + // Confirm that the CTA displays with the expected headings, text, buttons and other elements. $I->seeElementInDOM('#convertkit-restrict-content'); diff --git a/wp-convertkit.php b/wp-convertkit.php index 3e15d62fd..4641bce42 100644 --- a/wp-convertkit.php +++ b/wp-convertkit.php @@ -145,6 +145,9 @@ require_once CONVERTKIT_PLUGIN_PATH . '/includes/integrations/forminator/class-convertkit-forminator.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/integrations/forminator/class-convertkit-forminator-settings.php'; +// Impeka Integration. +require_once CONVERTKIT_PLUGIN_PATH . '/includes/integrations/class-convertkit-impeka.php'; + // Uncode Integration. require_once CONVERTKIT_PLUGIN_PATH . '/includes/integrations/class-convertkit-uncode.php';