diff --git a/admin/section/class-convertkit-admin-settings-broadcasts.php b/admin/section/class-convertkit-admin-settings-broadcasts.php index 82fe2eb8..53453e05 100644 --- a/admin/section/class-convertkit-admin-settings-broadcasts.php +++ b/admin/section/class-convertkit-admin-settings-broadcasts.php @@ -357,7 +357,8 @@ public function enable_callback( $args ) { 'on', $this->settings->enabled(), // phpcs:ignore WordPress.Security.EscapeOutput $args['label'], // phpcs:ignore WordPress.Security.EscapeOutput - $args['description'] // phpcs:ignore WordPress.Security.EscapeOutput + $args['description'], // phpcs:ignore WordPress.Security.EscapeOutput + array( 'convertkit-conditional-display' ) ); } diff --git a/admin/section/class-convertkit-settings-base.php b/admin/section/class-convertkit-settings-base.php index e0273373..bb1923d5 100644 --- a/admin/section/class-convertkit-settings-base.php +++ b/admin/section/class-convertkit-settings-base.php @@ -436,6 +436,38 @@ public function get_text_field( $name, $value = '', $description = false, $css_c } + /** + * Returns a number field. + * + * @since 2.6.1 + * + * @param string $name Name. + * @param string $value Value. + * @param int $min `min` attribute value. + * @param int $max `max` attribute value. + * @param int $step `step` attribute value. + * @param bool|string|array $description Description (false|string|array). + * @param bool|array $css_classes CSS Classes (false|array). + * @return string HTML Field + */ + public function get_number_field( $name, $value = '', $min = 0, $max = 9999, $step = 1, $description = false, $css_classes = false ) { + + $html = sprintf( + '', + ( is_array( $css_classes ) ? implode( ' ', $css_classes ) : 'small-text' ), + $name, + $this->settings_key, + $name, + $value, + $min, + $max, + $step + ); + + return $html . $this->get_description( $description ); + + } + /** * Returns a textarea field. * diff --git a/admin/section/class-convertkit-settings-general.php b/admin/section/class-convertkit-settings-general.php index b6ccbd19..16fe8259 100644 --- a/admin/section/class-convertkit-settings-general.php +++ b/admin/section/class-convertkit-settings-general.php @@ -233,8 +233,9 @@ public function enqueue_scripts( $section ) { // Enqueue Select2 JS. convertkit_select2_enqueue_scripts(); - // Enqueue Preview Output JS. + // Enqueue JS. wp_enqueue_script( 'convertkit-admin-preview-output', CONVERTKIT_PLUGIN_URL . 'resources/backend/js/preview-output.js', array( 'jquery' ), CONVERTKIT_PLUGIN_VERSION, true ); + wp_enqueue_script( 'convertkit-admin-settings-conditional-display', CONVERTKIT_PLUGIN_URL . 'resources/backend/js/settings-conditional-display.js', array( 'jquery' ), CONVERTKIT_PLUGIN_VERSION, true ); } @@ -307,7 +308,19 @@ public function register_fields() { $this->settings_key, $this->name, array( - 'label_for' => '_wp_convertkit_settings_' . $supported_post_type . '_form', + 'label_for' => '_wp_convertkit_settings_' . $supported_post_type . '_form_position', + 'post_type' => $supported_post_type, + 'post_type_object' => $post_type, + ) + ); + add_settings_field( + $supported_post_type . '_form_position_element', + '', + array( $this, 'default_form_position_element_callback' ), + $this->settings_key, + $this->name, + array( + 'label_for' => '_wp_convertkit_settings_' . $supported_post_type . '_form_position_element', 'post_type' => $supported_post_type, 'post_type_object' => $post_type, ) @@ -461,7 +474,6 @@ public function maybe_initialize_and_refresh_resources() { $tags = new ConvertKit_Resource_Tags( 'settings' ); $tags->refresh(); - } /** @@ -544,19 +556,68 @@ public function default_form_position_callback( $args ) { $args['post_type'] . '_form_position', esc_attr( $this->settings->get_default_form_position( $args['post_type'] ) ), array( - 'before_content' => esc_html__( 'Before content', 'convertkit' ), - 'after_content' => esc_html__( 'After content', 'convertkit' ), - 'before_after_content' => esc_html__( 'Before and after content', 'convertkit' ), + 'before_content' => sprintf( + /* translators: Post type singular name */ + esc_attr__( 'Before %s content', 'convertkit' ), + esc_attr( $args['post_type_object']->labels->singular_name ) + ), + 'after_content' => sprintf( + /* translators: Post type singular name */ + esc_attr__( 'After %s content', 'convertkit' ), + esc_attr( $args['post_type_object']->labels->singular_name ) + ), + 'before_after_content' => sprintf( + /* translators: Post type singular name */ + esc_attr__( 'Before and after %s content', 'convertkit' ), + esc_attr( $args['post_type_object']->labels->singular_name ) + ), + 'after_element' => esc_html__( 'After element', 'convertkit' ), ), sprintf( /* translators: Post Type name, plural */ esc_html__( 'Where forms should display relative to the %s content', 'convertkit' ), - esc_html( $args['post_type_object']->label ) + esc_html( $args['post_type_object']->labels->singular_name ) + ), + array( 'convertkit-conditional-display' ), + array( + 'data-conditional-value' => 'after_element', + 'data-conditional-element' => esc_attr( $args['post_type'] ) . '_form_position_element_index', ) ); } + /** + * Renders the input for the Default Form Position Index setting for the given Post Type. + * + * @since 2.6.1 + * + * @param array $args Field arguments. + */ + public function default_form_position_element_callback( $args ) { + + echo $this->get_number_field( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $args['post_type'] . '_form_position_element_index', + esc_attr( (string) $this->settings->get_default_form_position_element_index( $args['post_type'] ) ), + 1, + 999, + 1, + false, + array( 'after_element' ) + ); + + echo $this->get_select_field( // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $args['post_type'] . '_form_position_element', + esc_attr( $this->settings->get_default_form_position_element( $args['post_type'] ) ), + array( + 'p' => esc_html__( 'Paragraphs', 'convertkit' ), + ), + esc_html__( 'The number of elements before outputting the form.', 'convertkit' ), + array( 'after_element' ) + ); + + } + /** * Renders the input for the Non-inline Form setting. * diff --git a/includes/class-convertkit-output.php b/includes/class-convertkit-output.php index cd3a8641..d937ac45 100644 --- a/includes/class-convertkit-output.php +++ b/includes/class-convertkit-output.php @@ -338,6 +338,22 @@ public function append_form_to_content( $content ) { $content = $form . $content; break; + case 'after_element': + $element = $this->settings->get_default_form_position_element( get_post_type( $post_id ) ); + $index = $this->settings->get_default_form_position_element_index( get_post_type( $post_id ) ); + + // Check if DOMDocument is installed. + // It should be installed as mosts hosts include php-dom and php-xml modules. + // If not, fallback to using preg_match_all(), which is less reliable. + if ( ! class_exists( 'DOMDocument' ) ) { + $content = $this->inject_form_after_element_fallback( $content, $element, $index, $form ); + break; + } + + // Use DOMDocument. + $content = $this->inject_form_after_element( $content, $element, $index, $form ); + break; + case 'after_content': default: // Default behaviour < 2.5.8 was to append the Form after the content. @@ -362,6 +378,112 @@ public function append_form_to_content( $content ) { } + /** + * Injects the form after the given element and index, using DOMDocument. + * + * @since 2.6.2 + * + * @param string $content Page / Post Content. + * @param string $tag HTML tag to insert form after. + * @param int $index Number of $tag elements to find before inserting form. + * @param string $form Form HTML to inject. + * @return string + */ + private function inject_form_after_element( $content, $tag, $index, $form ) { + + // Load Page / Post content into DOMDocument. + libxml_use_internal_errors( true ); + $html = new DOMDocument(); + $html->loadHTML( $content, LIBXML_HTML_NODEFDTD ); + + // Find the element to append the form to. + // item() is a zero based index. + $element_node = $html->getElementsByTagName( $tag )->item( $index - 1 ); + + // If the element could not be found, either the number of elements by tag name is less + // than the requested position the form be inserted in, or no element exists. + // Append the form to the content and return. + if ( is_null( $element_node ) ) { + return $content . $form; + } + + // Create new element for the Form. + $form_node = new DOMDocument(); + $form_node->loadHTML( $form, LIBXML_HTML_NODEFDTD ); + + // Append the form to the specific element. + $element_node->parentNode->insertBefore( $html->importNode( $form_node->documentElement, true ), $element_node->nextSibling ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase + + // Fetch HTML string. + $content = $html->saveHTML(); + + // Remove some HTML tags that DOMDocument adds, returning the output. + // We do this instead of using LIBXML_HTML_NOIMPLIED in loadHTML(), because Legacy Forms are not always contained in + // a single root / outer element, which is required for LIBXML_HTML_NOIMPLIED to correctly work. + $content = str_replace( '', '', $content ); + $content = str_replace( '', '', $content ); + $content = str_replace( '', '', $content ); + $content = str_replace( '', '', $content ); + $content = str_replace( '', '', $content ); + $content = str_replace( '', '', $content ); + + return $content; + + } + + /** + * Injects the form after the given element and index, using preg_match_all(). + * This is less reliable than DOMDocument, and is called if DOMDocument is + * not installed on the server. + * + * @since 2.6.2 + * + * @param string $content Page / Post Content. + * @param string $tag HTML tag to insert form after. + * @param int $index Number of $tag elements to find before inserting form. + * @param string $form Form HTML to inject. + * @return string + */ + private function inject_form_after_element_fallback( $content, $tag, $index, $form ) { + + // Calculate tag length. + $tag_length = ( strlen( $tag ) + 3 ); + + // Find all closing elements. + preg_match_all( '/<\/' . $tag . '>/', $content, $matches ); + + // If no elements exist, just append the form. + if ( count( $matches[0] ) === 0 ) { + $content = $content . $form; + return $content; + } + + // If the number of elements is less than the index, we don't have enough elements to add the form to. + // Just add the form after the content. + if ( count( $matches[0] ) <= $index ) { + $content = $content . $form; + return $content; + } + + // Iterate through the content to find the element at the configured index e.g. find the 4th closing paragraph. + $offset = 0; + foreach ( $matches[0] as $element_index => $element ) { + $position = strpos( $content, $element, $offset ); + if ( ( $element_index + 1 ) === $index ) { + return substr( $content, 0, $position + 4 ) . $form . substr( $content, $position + 4 ); + } + + // Increment offset. + $offset = $position + 1; + } + + // If here, something went wrong. + // Just add the form after the content. + $content = $content . $form; + return $content; + + } + /** * Registers the ConvertKit Form block to before or after the Query Loop block, when viewing a Category archive. * diff --git a/includes/class-convertkit-settings.php b/includes/class-convertkit-settings.php index 92ccfa59..542b2ba5 100644 --- a/includes/class-convertkit-settings.php +++ b/includes/class-convertkit-settings.php @@ -307,6 +307,44 @@ public function get_default_form_position( $post_type ) { } + /** + * Returns the Default Form Position Element Plugin setting. + * + * @since 2.6.1 + * + * @param string $post_type Post Type. + * @return string Element to insert form after + */ + public function get_default_form_position_element( $post_type ) { + + // Return after_content if this Post Type's position doesn't exist as a setting. + if ( ! array_key_exists( $post_type . '_form_position_element', $this->settings ) ) { + return 'p'; + } + + return $this->settings[ $post_type . '_form_position_element' ]; + + } + + /** + * Returns the Default Form Position Index Plugin setting. + * + * @since 2.6.1 + * + * @param string $post_type Post Type. + * @return int Number of elements before inserting form + */ + public function get_default_form_position_element_index( $post_type ) { + + // Return 1 if this Post Type's position index doesn't exist as a setting. + if ( ! array_key_exists( $post_type . '_form_position_element_index', $this->settings ) ) { + return 1; + } + + return (int) $this->settings[ $post_type . '_form_position_element_index' ]; + + } + /** * Returns the Global non-inline Form Plugin setting. * @@ -406,8 +444,10 @@ public function get_defaults() { // Add Post Type Default Forms. foreach ( convertkit_get_supported_post_types() as $post_type ) { - $defaults[ $post_type . '_form' ] = 0; // -1, 0 or Form ID. - $defaults[ $post_type . '_form_position' ] = 'after_content'; // before_content,after_content. + $defaults[ $post_type . '_form' ] = 0; // -1, 0 or Form ID. + $defaults[ $post_type . '_form_position' ] = 'after_content'; // before_content,after_content,before_after_content,element. + $defaults[ $post_type . '_form_position_element' ] = 'p'; + $defaults[ $post_type . '_form_position_element_index' ] = 1; } /** diff --git a/resources/backend/js/settings-conditional-display.js b/resources/backend/js/settings-conditional-display.js index cea60200..57e5ebd9 100644 --- a/resources/backend/js/settings-conditional-display.js +++ b/resources/backend/js/settings-conditional-display.js @@ -17,16 +17,20 @@ document.addEventListener( function () { // Update settings and refresh UI when a setting is changed. - const enabledInput = document.querySelector( 'input#enabled' ); - enabledInput.addEventListener( - 'change', - function () { - convertKitConditionallyDisplaySettings( this.id, this.checked ); + const sourceInputs = document.querySelectorAll( '.convertkit-conditional-display' ); + sourceInputs.forEach( + function ( input ) { + input.addEventListener( + 'change', + function () { + convertKitConditionallyDisplaySettings( this ); + } + ); + + convertKitConditionallyDisplaySettings( input ); } ); - convertKitConditionallyDisplaySettings( 'enabled', enabledInput.checked ); - } ); @@ -35,31 +39,58 @@ document.addEventListener( * table rows related to a setting, if that setting is disabled. * * @since 2.2.4 + * + * @param object input Element interacted with */ -function convertKitConditionallyDisplaySettings( name, display ) { +function convertKitConditionallyDisplaySettings( input ) { - // Show all rows. const rows = document.querySelectorAll( 'table.form-table tr' ); - rows.forEach( row => row.style.display = '' ); - // Don't do anything else if display is true. - if ( display ) { - return; - } + switch ( input.type ) { + case 'checkbox': + // Show all rows. + rows.forEach( row => row.style.display = '' ); - // Iterate through the table rows, hiding any settings. - rows.forEach( - function ( row ) { - // Skip if this table row is for the setting we've just checked/unchecked. - if ( row.querySelector( `[id = "${name}"]` ) ) { - return; + // Don't do anything else if the checkbox is checked. + if ( input.checked ) { + return; } - // Hide this row if the input, select, link or span element within the row has the CSS class of the setting name. - if ( row.querySelector( `input.${name}, select.${name}, a.${name}, span.${name}` ) ) { - row.style.display = 'none'; - } - } - ); + // Iterate through the table rows, hiding any settings. + rows.forEach( + function ( row ) { + // Skip if this table row is for the setting we've just checked/unchecked. + if ( row.querySelector( `[id = "${input.id}"]` ) ) { + return; + } + + // Hide this row if the input, select, link or span element within the row has the CSS class of the setting ID. + if ( row.querySelector( `input.${input.id}, select.${input.id}, a.${input.id}, span.${input.id}` ) ) { + row.style.display = 'none'; + } + } + ); + break; + + default: + // Iterate through the table rows, hiding any settings. + rows.forEach( + function ( row ) { + // Skip if this table row is for the setting we've just changed. + if ( row.querySelector( `[id = "${input.id}"]` ) ) { + return; + } + + if ( row.querySelector( `input#${input.dataset.conditionalElement}` ) ) { + if ( input.value !== input.dataset.conditionalValue ) { + row.style.display = 'none'; + } else { + row.style.display = ''; + } + } + } + ); + break; + } } diff --git a/tests/_support/Helper/Acceptance/ConvertKitForms.php b/tests/_support/Helper/Acceptance/ConvertKitForms.php index 64bdd2e1..e85849c7 100644 --- a/tests/_support/Helper/Acceptance/ConvertKitForms.php +++ b/tests/_support/Helper/Acceptance/ConvertKitForms.php @@ -18,8 +18,10 @@ class ConvertKitForms extends \Codeception\Module * @param AcceptanceTester $I Tester. * @param int $formID Form ID. * @param bool|string $position Position of the form in the DOM relative to the content. + * @param bool|string $element Element the form should display after. + * @param bool|string $element_index Number of elements before the form should display. */ - public function seeFormOutput($I, $formID, $position = false) + public function seeFormOutput($I, $formID, $position = false, $element = false, $element_index = 0) { // Calculate how many times the Form should be in the DOM. $count = ( ( $position === 'before_after_content' ) ? 2 : 1 ); @@ -46,6 +48,10 @@ public function seeFormOutput($I, $formID, $position = false) case 'after_content': $I->assertEquals($formID, $I->grabAttributeFrom('div.entry-content > *:last-child', 'data-sv-form')); break; + + case 'after_element': + $I->seeInSource('<' . $element . '>Item #' . $element_index . '
seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID'], 'before_after_content'); } + /** + * Test that the Default Form specified in the Plugin Settings works when + * creating and viewing a new WordPress CPT, and its position is set + * to after the 3rd paragraph. + * + * @since 2.6.2 + * + * @param AcceptanceTester $I Tester. + */ + public function testAddNewCPTUsingDefaultFormAfterElement(AcceptanceTester $I) + { + // Setup ConvertKit plugin with Default Form for CPTs set to be output after the 3rd paragraph of content. + $I->setupConvertKitPlugin( + $I, + [ + 'article_form' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'article_form_position' => 'after_element', + 'article_form_position_element' => 'p', + 'article_form_position_element_index' => 3, + ] + ); + $I->setupConvertKitPluginResources($I); + + // Add a CPT using the Gutenberg editor. + $I->addGutenbergPage($I, 'article', 'Kit: CPT: Form: Default: After 3rd Paragraph Element'); + + // Add 5 paragraphs to CPT. + $I->addGutenbergParagraphBlock($I, 'Item #1'); + $I->addGutenbergParagraphBlock($I, 'Item #2'); + $I->addGutenbergParagraphBlock($I, 'Item #3'); + $I->addGutenbergParagraphBlock($I, 'Item #4'); + $I->addGutenbergParagraphBlock($I, 'Item #5'); + + // Configure metabox's Form setting = Default. + $I->configureMetaboxSettings( + $I, + 'wp-convertkit-meta-box', + [ + 'form' => [ 'select2', 'Default' ], + ] + ); + + // Publish and view the CPT on the frontend site. + $I->publishAndViewGutenbergPage($I); + + // Confirm that one ConvertKit Form is output in the DOM after the third paragraph. + $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID'], 'after_element', 'p', 3); + } + + /** + * Test that the Default Form specified in the Plugin Settings works when + * creating and viewing a new WordPress CPT, and its position is set + * to a number greater than the number of elements in the content. + * + * @since 2.6.2 + * + * @param AcceptanceTester $I Tester. + */ + public function testAddNewCPTUsingDefaultFormAfterOutOfBoundsElement(AcceptanceTester $I) + { + // Setup ConvertKit plugin with Default Form for CPTs set to be output after the 7th paragraph of content. + $I->setupConvertKitPlugin( + $I, + [ + 'article_form' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'article_form_position' => 'after_element', + 'article_form_position_element' => 'p', + 'article_form_position_element_index' => 7, + ] + ); + $I->setupConvertKitPluginResources($I); + + // Add a CPT using the Gutenberg editor. + $I->addGutenbergPage($I, 'article', 'Kit: CPT: Form: Default: After 7th Paragraph Element'); + + // Add 5 paragraphs to CPT. + $I->addGutenbergParagraphBlock($I, 'Item #1'); + $I->addGutenbergParagraphBlock($I, 'Item #2'); + $I->addGutenbergParagraphBlock($I, 'Item #3'); + + // Configure metabox's Form setting = Default. + $I->configureMetaboxSettings( + $I, + 'wp-convertkit-meta-box', + [ + 'form' => [ 'select2', 'Default' ], + ] + ); + + // Publish and view the CPT on the frontend site. + $I->publishAndViewGutenbergPage($I); + + // Confirm that one ConvertKit Form is output in the DOM after the content, as + // the number of paragraphs is less than the position. + $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID'], 'after_content'); + } + /** * Test that the Default Legacy Form specified in the Plugin Settings works when * creating and viewing a new WordPress CPT. diff --git a/tests/acceptance/forms/post-types/PageFormCest.php b/tests/acceptance/forms/post-types/PageFormCest.php index cce3571d..9a7b0f95 100644 --- a/tests/acceptance/forms/post-types/PageFormCest.php +++ b/tests/acceptance/forms/post-types/PageFormCest.php @@ -204,6 +204,103 @@ public function testAddNewPageUsingDefaultFormBeforeAndAfterContent(AcceptanceTe $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID'], 'before_after_content'); } + /** + * Test that the Default Form specified in the Plugin Settings works when + * creating and viewing a new WordPress Page, and its position is set + * to after the 3rd paragraph. + * + * @since 2.6.2 + * + * @param AcceptanceTester $I Tester. + */ + public function testAddNewPageUsingDefaultFormAfterElement(AcceptanceTester $I) + { + // Setup ConvertKit plugin with Default Form for Pages set to be output after the 3rd paragraph of content. + $I->setupConvertKitPlugin( + $I, + [ + 'page_form' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'page_form_position' => 'after_element', + 'page_form_position_element' => 'p', + 'page_form_position_element_index' => 3, + ] + ); + $I->setupConvertKitPluginResources($I); + + // Add a Page using the Gutenberg editor. + $I->addGutenbergPage($I, 'page', 'Kit: Page: Form: Default: After 3rd Paragraph Element'); + + // Add 5 paragraphs to Page. + $I->addGutenbergParagraphBlock($I, 'Item #1'); + $I->addGutenbergParagraphBlock($I, 'Item #2'); + $I->addGutenbergParagraphBlock($I, 'Item #3'); + $I->addGutenbergParagraphBlock($I, 'Item #4'); + $I->addGutenbergParagraphBlock($I, 'Item #5'); + + // Configure metabox's Form setting = Default. + $I->configureMetaboxSettings( + $I, + 'wp-convertkit-meta-box', + [ + 'form' => [ 'select2', 'Default' ], + ] + ); + + // Publish and view the Page on the frontend site. + $I->publishAndViewGutenbergPage($I); + + // Confirm that one ConvertKit Form is output in the DOM after the third paragraph. + $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID'], 'after_element', 'p', 3); + } + + /** + * Test that the Default Form specified in the Plugin Settings works when + * creating and viewing a new WordPress Page, and its position is set + * to a number greater than the number of elements in the content. + * + * @since 2.6.2 + * + * @param AcceptanceTester $I Tester. + */ + public function testAddNewPageUsingDefaultFormAfterOutOfBoundsElement(AcceptanceTester $I) + { + // Setup ConvertKit plugin with Default Form for Pages set to be output after the 7rd paragraph of content. + $I->setupConvertKitPlugin( + $I, + [ + 'page_form' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'page_form_position' => 'after_element', + 'page_form_position_element' => 'p', + 'page_form_position_element_index' => 7, + ] + ); + $I->setupConvertKitPluginResources($I); + + // Add a Page using the Gutenberg editor. + $I->addGutenbergPage($I, 'page', 'Kit: Page: Form: Default: After 7th Paragraph Element'); + + // Add 5 paragraphs to Page. + $I->addGutenbergParagraphBlock($I, 'Item #1'); + $I->addGutenbergParagraphBlock($I, 'Item #2'); + $I->addGutenbergParagraphBlock($I, 'Item #3'); + + // Configure metabox's Form setting = Default. + $I->configureMetaboxSettings( + $I, + 'wp-convertkit-meta-box', + [ + 'form' => [ 'select2', 'Default' ], + ] + ); + + // Publish and view the Page on the frontend site. + $I->publishAndViewGutenbergPage($I); + + // Confirm that one ConvertKit Form is output in the DOM after the content, as + // the number of paragraphs is less than the position. + $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID'], 'after_content'); + } + /** * Test that the Default Legacy Form specified in the Plugin Settings works when * creating and viewing a new WordPress Page. diff --git a/tests/acceptance/forms/post-types/PostFormCest.php b/tests/acceptance/forms/post-types/PostFormCest.php index d2e48c92..98939dcc 100644 --- a/tests/acceptance/forms/post-types/PostFormCest.php +++ b/tests/acceptance/forms/post-types/PostFormCest.php @@ -203,6 +203,103 @@ public function testAddNewPostUsingDefaultFormBeforeAndAfterContent(AcceptanceTe $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID'], 'before_after_content'); } + /** + * Test that the Default Form specified in the Plugin Settings works when + * creating and viewing a new WordPress Post, and its position is set + * to after the 3rd paragraph. + * + * @since 2.6.2 + * + * @param AcceptanceTester $I Tester. + */ + public function testAddNewPostUsingDefaultFormAfterElement(AcceptanceTester $I) + { + // Setup ConvertKit plugin with Default Form for Posts set to be output after the 3rd paragraph of content. + $I->setupConvertKitPlugin( + $I, + [ + 'post_form' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'post_form_position' => 'after_element', + 'post_form_position_element' => 'p', + 'post_form_position_element_index' => 3, + ] + ); + $I->setupConvertKitPluginResources($I); + + // Add a Post using the Gutenberg editor. + $I->addGutenbergPage($I, 'post', 'Kit: Post: Form: Default: After 3rd Paragraph Element'); + + // Add 5 paragraphs to Post. + $I->addGutenbergParagraphBlock($I, 'Item #1'); + $I->addGutenbergParagraphBlock($I, 'Item #2'); + $I->addGutenbergParagraphBlock($I, 'Item #3'); + $I->addGutenbergParagraphBlock($I, 'Item #4'); + $I->addGutenbergParagraphBlock($I, 'Item #5'); + + // Configure metabox's Form setting = Default. + $I->configureMetaboxSettings( + $I, + 'wp-convertkit-meta-box', + [ + 'form' => [ 'select2', 'Default' ], + ] + ); + + // Publish and view the Post on the frontend site. + $I->publishAndViewGutenbergPage($I); + + // Confirm that one ConvertKit Form is output in the DOM after the third paragraph. + $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID'], 'after_element', 'p', 3); + } + + /** + * Test that the Default Form specified in the Plugin Settings works when + * creating and viewing a new WordPress Post, and its position is set + * to a number greater than the number of elements in the content. + * + * @since 2.6.2 + * + * @param AcceptanceTester $I Tester. + */ + public function testAddNewPostUsingDefaultFormAfterOutOfBoundsElement(AcceptanceTester $I) + { + // Setup ConvertKit plugin with Default Form for Posts set to be output after the 7rd paragraph of content. + $I->setupConvertKitPlugin( + $I, + [ + 'post_form' => $_ENV['CONVERTKIT_API_FORM_ID'], + 'post_form_position' => 'after_element', + 'post_form_position_element' => 'p', + 'post_form_position_element_index' => 7, + ] + ); + $I->setupConvertKitPluginResources($I); + + // Add a Post using the Gutenberg editor. + $I->addGutenbergPage($I, 'post', 'Kit: Post: Form: Default: After 7th Paragraph Element'); + + // Add 5 paragraphs to Post. + $I->addGutenbergParagraphBlock($I, 'Item #1'); + $I->addGutenbergParagraphBlock($I, 'Item #2'); + $I->addGutenbergParagraphBlock($I, 'Item #3'); + + // Configure metabox's Form setting = Default. + $I->configureMetaboxSettings( + $I, + 'wp-convertkit-meta-box', + [ + 'form' => [ 'select2', 'Default' ], + ] + ); + + // Publish and view the Post on the frontend site. + $I->publishAndViewGutenbergPage($I); + + // Confirm that one ConvertKit Form is output in the DOM after the content, as + // the number of paragraphs is less than the position. + $I->seeFormOutput($I, $_ENV['CONVERTKIT_API_FORM_ID'], 'after_content'); + } + /** * Test that the Default Legacy Form specified in the Plugin Settings works when * creating and viewing a new WordPress Post. diff --git a/tests/acceptance/general/PluginSettingsGeneralCest.php b/tests/acceptance/general/PluginSettingsGeneralCest.php index 84720f09..b15fa7fe 100644 --- a/tests/acceptance/general/PluginSettingsGeneralCest.php +++ b/tests/acceptance/general/PluginSettingsGeneralCest.php @@ -242,7 +242,7 @@ public function testChangeDefaultFormSettingAndPreviewFormLinks(AcceptanceTester // Select Default Form for Pages, and change the Position. $I->fillSelect2Field($I, '#select2-_wp_convertkit_settings_page_form-container', $_ENV['CONVERTKIT_API_FORM_NAME']); - $I->selectOption('_wp_convertkit_settings[page_form_position]', 'Before content'); + $I->selectOption('_wp_convertkit_settings[page_form_position]', 'Before Page content'); // Open preview. $I->click('a#convertkit-preview-form-page'); @@ -306,12 +306,79 @@ public function testChangeDefaultFormSettingAndPreviewFormLinks(AcceptanceTester // Check the value of the fields match the inputs provided. $I->seeInField('_wp_convertkit_settings[page_form]', $_ENV['CONVERTKIT_API_FORM_NAME']); - $I->seeInField('_wp_convertkit_settings[page_form_position]', 'Before content'); + $I->seeInField('_wp_convertkit_settings[page_form_position]', 'Before Page content'); $I->seeInField('_wp_convertkit_settings[post_form]', $_ENV['CONVERTKIT_API_FORM_NAME']); - $I->seeInField('_wp_convertkit_settings[post_form_position]', 'After content'); + $I->seeInField('_wp_convertkit_settings[post_form_position]', 'After Post content'); $I->seeInField('_wp_convertkit_settings[non_inline_form]', $_ENV['CONVERTKIT_API_FORM_FORMAT_STICKY_BAR_NAME']); } + /** + * Test that no PHP errors or notices are displayed on the Plugin's Setting screen, + * when the Default Form Position setting for Pages and Posts are changed. + * + * @since 2.6.2 + * + * @param AcceptanceTester $I Tester. + */ + public function testChangeDefaultFormPositionAfterElementSetting(AcceptanceTester $I) + { + // Setup Plugin, without defining default Forms. + $I->setupConvertKitPluginNoDefaultForms($I); + + // Go to the Plugin's Settings Screen. + $I->loadConvertKitSettingsGeneralScreen($I); + + // Confirm the conditional fields do not display for Pages, as the 'After element' is not selected. + $I->dontSeeElement('_wp_convertkit_settings[page_form_position_element_index]'); + $I->dontSeeElement('_wp_convertkit_settings[page_form_position_element]'); + + // Select Default Form for Pages, and change the Position. + $I->fillSelect2Field($I, '#select2-_wp_convertkit_settings_page_form-container', $_ENV['CONVERTKIT_API_FORM_NAME']); + $I->selectOption('_wp_convertkit_settings[page_form_position]', 'After element'); + + // Confirm the conditional fields display for Pages, now that 'After element' is selected. + $I->waitForElementVisible('input[name="_wp_convertkit_settings[page_form_position_element_index]"]'); + $I->waitForElementVisible('select[name="_wp_convertkit_settings[page_form_position_element]"]'); + + // Change a setting. + $I->fillField('_wp_convertkit_settings[page_form_position_element_index]', '3'); + + // Confirm the conditional fields do not display for Posts, as the 'After element' is not selected. + $I->dontSeeElement('input[name="_wp_convertkit_settings[post_form_position_element_index]"]'); + $I->dontSeeElement('select[name="_wp_convertkit_settings[post_form_position_element]"]'); + + // Select Default Form for Posts, and change the Position. + $I->fillSelect2Field($I, '#select2-_wp_convertkit_settings_post_form-container', $_ENV['CONVERTKIT_API_FORM_NAME']); + $I->selectOption('_wp_convertkit_settings[post_form_position]', 'After element'); + + // Confirm the conditional fields display for Posts, now that 'After element' is selected. + $I->waitForElementVisible('input[name="_wp_convertkit_settings[post_form_position_element_index]"]'); + $I->waitForElementVisible('select[name="_wp_convertkit_settings[post_form_position_element]"]'); + + // Change a setting. + $I->fillField('_wp_convertkit_settings[post_form_position_element_index]', '2'); + + // Click the Save Changes button. + $I->click('Save Changes'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Check the value of the fields match the inputs provided. + $I->seeInField('_wp_convertkit_settings[page_form_position]', 'After element'); + $I->seeInField('_wp_convertkit_settings[page_form_position_element]', 'Paragraphs'); + $I->seeInField('_wp_convertkit_settings[page_form_position_element_index]', '3'); + $I->seeInField('_wp_convertkit_settings[post_form_position]', 'After element'); + $I->seeInField('_wp_convertkit_settings[post_form_position_element]', 'Paragraphs'); + $I->seeInField('_wp_convertkit_settings[post_form_position_element_index]', '2'); + + // Check that the conditional fields display, as 'After element' is selected for Pages and Posts. + $I->seeElement('input[name="_wp_convertkit_settings[page_form_position_element_index]"]'); + $I->seeElement('select[name="_wp_convertkit_settings[page_form_position_element]"]'); + $I->seeElement('input[name="_wp_convertkit_settings[post_form_position_element_index]"]'); + $I->seeElement('select[name="_wp_convertkit_settings[post_form_position_element]"]'); + } + /** * Test that the settings screen does not display preview links * when no Pages and Posts exist in WordPress.