From 7d939be7f969ea4b1f9de76d40e3756c3e86efd4 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 11 Oct 2024 11:05:56 +0800 Subject: [PATCH 01/12] Started work on before/after paragraph option --- .../class-convertkit-settings-base.php | 29 +++++++++++ .../class-convertkit-settings-general.php | 50 +++++++++++++++++-- includes/class-convertkit-settings.php | 24 ++++++++- 3 files changed, 96 insertions(+), 7 deletions(-) diff --git a/admin/section/class-convertkit-settings-base.php b/admin/section/class-convertkit-settings-base.php index e0273373..bec7c986 100644 --- a/admin/section/class-convertkit-settings-base.php +++ b/admin/section/class-convertkit-settings-base.php @@ -436,6 +436,35 @@ 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 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 ) : 'regular-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..0a5dbb91 100644 --- a/admin/section/class-convertkit-settings-general.php +++ b/admin/section/class-convertkit-settings-general.php @@ -307,7 +307,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_index', + '', + array( $this, 'default_form_position_index_callback' ), + $this->settings_key, + $this->name, + array( + 'label_for' => '_wp_convertkit_settings_' . $supported_post_type . '_form_position_index', 'post_type' => $supported_post_type, 'post_type_object' => $post_type, ) @@ -544,19 +556,47 @@ 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( + esc_html__( 'Before %s content', 'convertkit' ), + $args['post_type_object']->labels->singular_name + ), + 'after_content' => sprintf( + esc_html__( 'After %s content', 'convertkit' ), + $args['post_type_object']->labels->singular_name + ), + 'before_after_content' => sprintf( + esc_html__( 'Before and after %s content', 'convertkit' ), + $args['post_type_object']->labels->singular_name + ), + 'before_paragraph' => esc_html__( 'Before paragraph', 'convertkit' ), + 'after_paragraph' => esc_html__( 'After paragraph', '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 ) ) ); } + /** + * 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_index_callback( $args ) { + + echo $this->get_text_field( // phpcs:ignore WordPress.Security.EscapeOutput + $args['post_type'] . '_form_position', + esc_attr( $this->settings->get_default_form_position_index( $args['post_type'] ) ), + esc_html__( 'The number of paragraphs before or after to display the form.', 'convertkit' ) + ); + + } + /** * Renders the input for the Non-inline Form setting. * diff --git a/includes/class-convertkit-settings.php b/includes/class-convertkit-settings.php index 92ccfa59..69a97e07 100644 --- a/includes/class-convertkit-settings.php +++ b/includes/class-convertkit-settings.php @@ -307,6 +307,25 @@ public function get_default_form_position( $post_type ) { } + /** + * Returns the Default Form Position Index Plugin setting. + * + * @since 2.6.1 + * + * @param string $post_type Post Type. + * @return int Position Index + */ + public function get_default_form_position_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_index', $this->settings ) ) { + return 1; + } + + return (int) $this->settings[ $post_type . '_form_position_index' ]; + + } + /** * Returns the Global non-inline Form Plugin setting. * @@ -406,8 +425,9 @@ 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. + $defaults[ $post_type . '_form_position_index' ] = 1; } /** From 190f08aea41be0c62ccea0c24f4aa8c4bb30f4fb Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 11 Oct 2024 16:05:29 +0800 Subject: [PATCH 02/12] Change conditional display to use a CSS class, so we can support other elements, not just checkboxes --- ...s-convertkit-admin-settings-broadcasts.php | 5 +- .../js/settings-conditional-display.js | 64 +++++++++++-------- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/admin/section/class-convertkit-admin-settings-broadcasts.php b/admin/section/class-convertkit-admin-settings-broadcasts.php index 82fe2eb8..0bcb7a41 100644 --- a/admin/section/class-convertkit-admin-settings-broadcasts.php +++ b/admin/section/class-convertkit-admin-settings-broadcasts.php @@ -206,7 +206,7 @@ public function register_fields() { 'label_for' => 'enabled', 'label' => __( 'Enables automatic publication of public Kit Broadcasts as WordPress Posts.', 'convertkit' ), 'description' => $enabled_description, - ) + ), ); // Render import button if the feature is enabled. @@ -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/resources/backend/js/settings-conditional-display.js b/resources/backend/js/settings-conditional-display.js index cea60200..44777029 100644 --- a/resources/backend/js/settings-conditional-display.js +++ b/resources/backend/js/settings-conditional-display.js @@ -17,15 +17,18 @@ 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' ); + console.log( sourceInputs ); + sourceInputs.forEach( function( input ) { + input.addEventListener( + 'change', + function () { + convertKitConditionallyDisplaySettings( this ); + } + ); - convertKitConditionallyDisplaySettings( 'enabled', enabledInput.checked ); + convertKitConditionallyDisplaySettings( input ); + } ); } ); @@ -35,31 +38,40 @@ 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; - } - - // 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; + switch ( input.type ) { + case 'checkbox': + // 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; + + case 'select': + break; + } } From 5b8f29abc603d31ca309787e1cbee39f272005d5 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 11 Oct 2024 17:30:23 +0800 Subject: [PATCH 03/12] Admin settings / UI --- .../class-convertkit-settings-base.php | 7 ++- .../class-convertkit-settings-general.php | 57 +++++++++++++------ includes/class-convertkit-settings.php | 34 ++++++++--- .../js/settings-conditional-display.js | 47 ++++++++++----- 4 files changed, 104 insertions(+), 41 deletions(-) diff --git a/admin/section/class-convertkit-settings-base.php b/admin/section/class-convertkit-settings-base.php index bec7c986..bb1923d5 100644 --- a/admin/section/class-convertkit-settings-base.php +++ b/admin/section/class-convertkit-settings-base.php @@ -443,15 +443,18 @@ public function get_text_field( $name, $value = '', $description = false, $css_c * * @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 + * @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 ) : 'regular-text' ), + ( is_array( $css_classes ) ? implode( ' ', $css_classes ) : 'small-text' ), $name, $this->settings_key, $name, diff --git a/admin/section/class-convertkit-settings-general.php b/admin/section/class-convertkit-settings-general.php index 0a5dbb91..80e03f6b 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 ); } @@ -313,13 +314,13 @@ public function register_fields() { ) ); add_settings_field( - $supported_post_type . '_form_position_index', + $supported_post_type . '_form_position_element', '', - array( $this, 'default_form_position_index_callback' ), + array( $this, 'default_form_position_element_callback' ), $this->settings_key, $this->name, array( - 'label_for' => '_wp_convertkit_settings_' . $supported_post_type . '_form_position_index', + 'label_for' => '_wp_convertkit_settings_' . $supported_post_type . '_form_position_element', 'post_type' => $supported_post_type, 'post_type_object' => $post_type, ) @@ -473,7 +474,6 @@ public function maybe_initialize_and_refresh_resources() { $tags = new ConvertKit_Resource_Tags( 'settings' ); $tags->refresh(); - } /** @@ -557,24 +557,31 @@ public function default_form_position_callback( $args ) { esc_attr( $this->settings->get_default_form_position( $args['post_type'] ) ), array( 'before_content' => sprintf( - esc_html__( 'Before %s content', 'convertkit' ), - $args['post_type_object']->labels->singular_name + /* translators: Post type singular name */ + esc_attr__( 'Before %s content', 'convertkit' ), + esc_attr( $args['post_type_object']->labels->singular_name ) ), 'after_content' => sprintf( - esc_html__( 'After %s content', 'convertkit' ), - $args['post_type_object']->labels->singular_name + /* translators: Post type singular name */ + esc_attr__( 'After %s content', 'convertkit' ), + esc_attr( $args['post_type_object']->labels->singular_name ) ), 'before_after_content' => sprintf( - esc_html__( 'Before and after %s content', 'convertkit' ), - $args['post_type_object']->labels->singular_name + /* translators: Post type singular name */ + esc_attr__( 'Before and after %s content', 'convertkit' ), + esc_attr( $args['post_type_object']->labels->singular_name ) ), - 'before_paragraph' => esc_html__( 'Before paragraph', 'convertkit' ), - 'after_paragraph' => esc_html__( 'After paragraph', 'convertkit' ), + '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']->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', ) ); @@ -587,12 +594,26 @@ public function default_form_position_callback( $args ) { * * @param array $args Field arguments. */ - public function default_form_position_index_callback( $args ) { + 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( $this->settings->get_default_form_position_element_index( $args['post_type'] ) ), + 1, + 999, + 1, + false, + array( 'after_element' ) + ); - echo $this->get_text_field( // phpcs:ignore WordPress.Security.EscapeOutput - $args['post_type'] . '_form_position', - esc_attr( $this->settings->get_default_form_position_index( $args['post_type'] ) ), - esc_html__( 'The number of paragraphs before or after to display the form.', 'convertkit' ) + 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' ) ); } diff --git a/includes/class-convertkit-settings.php b/includes/class-convertkit-settings.php index 69a97e07..542b2ba5 100644 --- a/includes/class-convertkit-settings.php +++ b/includes/class-convertkit-settings.php @@ -307,22 +307,41 @@ 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 Position Index + * @return int Number of elements before inserting form */ - public function get_default_form_position_index( $post_type ) { + 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_index', $this->settings ) ) { + if ( ! array_key_exists( $post_type . '_form_position_element_index', $this->settings ) ) { return 1; } - return (int) $this->settings[ $post_type . '_form_position_index' ]; + return (int) $this->settings[ $post_type . '_form_position_element_index' ]; } @@ -425,9 +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_position_index' ] = 1; + $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 44777029..4c8ee4ff 100644 --- a/resources/backend/js/settings-conditional-display.js +++ b/resources/backend/js/settings-conditional-display.js @@ -18,17 +18,18 @@ document.addEventListener( // Update settings and refresh UI when a setting is changed. const sourceInputs = document.querySelectorAll( '.convertkit-conditional-display' ); - console.log( sourceInputs ); - sourceInputs.forEach( function( input ) { - input.addEventListener( - 'change', - function () { - convertKitConditionallyDisplaySettings( this ); - } - ); + sourceInputs.forEach( + function ( input ) { + input.addEventListener( + 'change', + function () { + convertKitConditionallyDisplaySettings( this ); + } + ); - convertKitConditionallyDisplaySettings( input ); - } ); + convertKitConditionallyDisplaySettings( input ); + } + ); } ); @@ -38,17 +39,18 @@ 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( input ) { - // Show all rows. const rows = document.querySelectorAll( 'table.form-table tr' ); - rows.forEach( row => row.style.display = '' ); switch ( input.type ) { case 'checkbox': + // Show all rows. + rows.forEach( row => row.style.display = '' ); + // Don't do anything else if the checkbox is checked. if ( input.checked ) { return; @@ -70,7 +72,24 @@ function convertKitConditionallyDisplaySettings( input ) { ); break; - case 'select': + case 'select-one': + // 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; } From 61afbd29b41950e48abfd328c4206663453c8606 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 11 Oct 2024 17:41:45 +0800 Subject: [PATCH 04/12] First pass at output functionality --- includes/class-convertkit-output.php | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/includes/class-convertkit-output.php b/includes/class-convertkit-output.php index cd3a8641..00418cb2 100644 --- a/includes/class-convertkit-output.php +++ b/includes/class-convertkit-output.php @@ -338,6 +338,35 @@ public function append_form_to_content( $content ) { $content = $form . $content; break; + case 'after_element': + $form_position_element = $this->settings->get_default_form_position_element( get_post_type( $post_id ) ); + $form_position_element_index = $this->settings->get_default_form_position_element_index( get_post_type( $post_id ) ); + $form_position_element_tag_length = ( strlen( $form_position_element ) + 3 ); + + // Find all closing elements. + preg_match_all( '/<\/' . $form_position_element . '>/', $content, $matches ); + + // 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] ) <= $form_position_element_index ) { + $content = $content . $form; + break; + } + + // 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 === $form_position_element_index ) { + $content = substr( $content, 0, $position + 4 ) . $form . substr( $content, $position + 4 ); + break; + } + + // Increment offset. + $offset = $position + 1; + } + break; + case 'after_content': default: // Default behaviour < 2.5.8 was to append the Form after the content. From dd839704e1b80ffed72034365d1e98a05c870f7e Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 11 Oct 2024 19:40:04 +0800 Subject: [PATCH 05/12] Added Settings UI Test --- includes/class-convertkit-output.php | 2 +- .../general/PluginSettingsGeneralCest.php | 67 +++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/includes/class-convertkit-output.php b/includes/class-convertkit-output.php index 00418cb2..6ca7712d 100644 --- a/includes/class-convertkit-output.php +++ b/includes/class-convertkit-output.php @@ -357,7 +357,7 @@ public function append_form_to_content( $content ) { $offset = 0; foreach ( $matches[0] as $element_index => $element ) { $position = strpos( $content, $element, $offset ); - if ( $element_index === $form_position_element_index ) { + if ( ( $element_index + 1 ) === $form_position_element_index ) { $content = substr( $content, 0, $position + 4 ) . $form . substr( $content, $position + 4 ); break; } diff --git a/tests/acceptance/general/PluginSettingsGeneralCest.php b/tests/acceptance/general/PluginSettingsGeneralCest.php index 84720f09..4c36bbfc 100644 --- a/tests/acceptance/general/PluginSettingsGeneralCest.php +++ b/tests/acceptance/general/PluginSettingsGeneralCest.php @@ -312,6 +312,73 @@ public function testChangeDefaultFormSettingAndPreviewFormLinks(AcceptanceTester $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. From df10498ff5a7d13b173bd75cc0cb2179839cde6f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 11 Oct 2024 20:44:36 +0800 Subject: [PATCH 06/12] Completed Tests --- .../class-convertkit-settings-general.php | 2 +- .../Helper/Acceptance/ConvertKitForms.php | 8 ++- .../forms/post-types/CPTFormCest.php | 49 +++++++++++++++++++ .../forms/post-types/PageFormCest.php | 49 +++++++++++++++++++ .../forms/post-types/PostFormCest.php | 49 +++++++++++++++++++ .../general/PluginSettingsGeneralCest.php | 4 +- 6 files changed, 157 insertions(+), 4 deletions(-) diff --git a/admin/section/class-convertkit-settings-general.php b/admin/section/class-convertkit-settings-general.php index 80e03f6b..16fe8259 100644 --- a/admin/section/class-convertkit-settings-general.php +++ b/admin/section/class-convertkit-settings-general.php @@ -598,7 +598,7 @@ 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( $this->settings->get_default_form_position_element_index( $args['post_type'] ) ), + esc_attr( (string) $this->settings->get_default_form_position_element_index( $args['post_type'] ) ), 1, 999, 1, 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 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..51479d80 100644 --- a/tests/acceptance/forms/post-types/PageFormCest.php +++ b/tests/acceptance/forms/post-types/PageFormCest.php @@ -204,6 +204,55 @@ 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 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..8e99ce03 100644 --- a/tests/acceptance/forms/post-types/PostFormCest.php +++ b/tests/acceptance/forms/post-types/PostFormCest.php @@ -203,6 +203,55 @@ 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 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 4c36bbfc..713b813e 100644 --- a/tests/acceptance/general/PluginSettingsGeneralCest.php +++ b/tests/acceptance/general/PluginSettingsGeneralCest.php @@ -331,7 +331,7 @@ public function testChangeDefaultFormPositionAfterElementSetting(AcceptanceTeste // 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'); @@ -346,7 +346,7 @@ public function testChangeDefaultFormPositionAfterElementSetting(AcceptanceTeste // 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'); From 9f9d9b9365d7471f76a0aa6433a860e2e838cd4c Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 11 Oct 2024 20:51:09 +0800 Subject: [PATCH 07/12] Fix failing tests due to option label changes --- tests/acceptance/general/PluginSettingsGeneralCest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/acceptance/general/PluginSettingsGeneralCest.php b/tests/acceptance/general/PluginSettingsGeneralCest.php index 713b813e..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,9 +306,9 @@ 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']); } From 5670ec9c85a1e8c81a2ed7619e29db12f0bde85f Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Fri, 11 Oct 2024 21:08:56 +0800 Subject: [PATCH 08/12] Coding standard fixes --- admin/section/class-convertkit-admin-settings-broadcasts.php | 2 +- resources/backend/js/settings-conditional-display.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/admin/section/class-convertkit-admin-settings-broadcasts.php b/admin/section/class-convertkit-admin-settings-broadcasts.php index 0bcb7a41..53453e05 100644 --- a/admin/section/class-convertkit-admin-settings-broadcasts.php +++ b/admin/section/class-convertkit-admin-settings-broadcasts.php @@ -206,7 +206,7 @@ public function register_fields() { 'label_for' => 'enabled', 'label' => __( 'Enables automatic publication of public Kit Broadcasts as WordPress Posts.', 'convertkit' ), 'description' => $enabled_description, - ), + ) ); // Render import button if the feature is enabled. diff --git a/resources/backend/js/settings-conditional-display.js b/resources/backend/js/settings-conditional-display.js index 4c8ee4ff..57e5ebd9 100644 --- a/resources/backend/js/settings-conditional-display.js +++ b/resources/backend/js/settings-conditional-display.js @@ -72,7 +72,7 @@ function convertKitConditionallyDisplaySettings( input ) { ); break; - case 'select-one': + default: // Iterate through the table rows, hiding any settings. rows.forEach( function ( row ) { From 37ec6a6b9b49e7c9d7dc196bb8ae4ea0ddabad75 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 15 Oct 2024 12:20:22 +0800 Subject: [PATCH 09/12] Use DOMDocument to parse content and inject form --- includes/class-convertkit-output.php | 134 ++++++++++++++++++++++----- 1 file changed, 111 insertions(+), 23 deletions(-) diff --git a/includes/class-convertkit-output.php b/includes/class-convertkit-output.php index 6ca7712d..d762dacd 100644 --- a/includes/class-convertkit-output.php +++ b/includes/class-convertkit-output.php @@ -339,32 +339,19 @@ public function append_form_to_content( $content ) { break; case 'after_element': - $form_position_element = $this->settings->get_default_form_position_element( get_post_type( $post_id ) ); - $form_position_element_index = $this->settings->get_default_form_position_element_index( get_post_type( $post_id ) ); - $form_position_element_tag_length = ( strlen( $form_position_element ) + 3 ); - - // Find all closing elements. - preg_match_all( '/<\/' . $form_position_element . '>/', $content, $matches ); - - // 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] ) <= $form_position_element_index ) { - $content = $content . $form; + $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; } - // 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 ) === $form_position_element_index ) { - $content = substr( $content, 0, $position + 4 ) . $form . substr( $content, $position + 4 ); - break; - } - - // Increment offset. - $offset = $position + 1; - } + // Use DOMDocument. + $content = $this->inject_form_after_element( $content, $element, $index, $form ); break; case 'after_content': @@ -391,6 +378,107 @@ 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 string $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 ); + + // Create new element for the Form. + $form_node = new DOMDocument(); + $form_node->loadHTML( $form, LIBXML_HTML_NODEFDTD ); + + // Append the form to the specific element. + // If the index is greater than the number of elements that exist in the content, this will append the form + // to the end of the content. + $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 string $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. * From 6501f17498a857c8f86937092e324fe063a3e120 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 15 Oct 2024 12:46:22 +0800 Subject: [PATCH 10/12] Append form after content when index is out of bounds or element does not exist --- includes/class-convertkit-output.php | 9 +++- .../forms/post-types/CPTFormCest.php | 48 +++++++++++++++++++ .../forms/post-types/PageFormCest.php | 48 +++++++++++++++++++ .../forms/post-types/PostFormCest.php | 48 +++++++++++++++++++ 4 files changed, 151 insertions(+), 2 deletions(-) diff --git a/includes/class-convertkit-output.php b/includes/class-convertkit-output.php index d762dacd..56986177 100644 --- a/includes/class-convertkit-output.php +++ b/includes/class-convertkit-output.php @@ -400,13 +400,18 @@ private function inject_form_after_element( $content, $tag, $index, $form ) { // 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. - // If the index is greater than the number of elements that exist in the content, this will append the form - // to the end of the content. $element_node->parentNode->insertBefore( $html->importNode( $form_node->documentElement, true ), $element_node->nextSibling ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase // Fetch HTML string. diff --git a/tests/acceptance/forms/post-types/CPTFormCest.php b/tests/acceptance/forms/post-types/CPTFormCest.php index c34c81a8..3e8ffe6a 100644 --- a/tests/acceptance/forms/post-types/CPTFormCest.php +++ b/tests/acceptance/forms/post-types/CPTFormCest.php @@ -329,6 +329,54 @@ public function testAddNewCPTUsingDefaultFormAfterElement(AcceptanceTester $I) $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 51479d80..9a7b0f95 100644 --- a/tests/acceptance/forms/post-types/PageFormCest.php +++ b/tests/acceptance/forms/post-types/PageFormCest.php @@ -253,6 +253,54 @@ public function testAddNewPageUsingDefaultFormAfterElement(AcceptanceTester $I) $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 8e99ce03..98939dcc 100644 --- a/tests/acceptance/forms/post-types/PostFormCest.php +++ b/tests/acceptance/forms/post-types/PostFormCest.php @@ -252,6 +252,54 @@ public function testAddNewPostUsingDefaultFormAfterElement(AcceptanceTester $I) $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. From f6d555cf36fcf6a6c0ae064bc9c6e6584fe418b2 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 15 Oct 2024 13:12:54 +0800 Subject: [PATCH 11/12] PHPStan compat. --- includes/class-convertkit-output.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-convertkit-output.php b/includes/class-convertkit-output.php index 56986177..73c94ed6 100644 --- a/includes/class-convertkit-output.php +++ b/includes/class-convertkit-output.php @@ -385,7 +385,7 @@ public function append_form_to_content( $content ) { * * @param string $content Page / Post Content. * @param string $tag HTML tag to insert form after. - * @param string $index Number of $tag elements to find before inserting form. + * @param int $index Number of $tag elements to find before inserting form. * @param string $form Form HTML to inject. * @return string */ @@ -440,7 +440,7 @@ private function inject_form_after_element( $content, $tag, $index, $form ) { * * @param string $content Page / Post Content. * @param string $tag HTML tag to insert form after. - * @param string $index Number of $tag elements to find before inserting form. + * @param int $index Number of $tag elements to find before inserting form. * @param string $form Form HTML to inject. * @return string */ From 9747149f54e7627bab9f4e15be494d211ca223bb Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 15 Oct 2024 13:23:51 +0800 Subject: [PATCH 12/12] Coding standards --- includes/class-convertkit-output.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-convertkit-output.php b/includes/class-convertkit-output.php index 73c94ed6..d937ac45 100644 --- a/includes/class-convertkit-output.php +++ b/includes/class-convertkit-output.php @@ -440,7 +440,7 @@ private function inject_form_after_element( $content, $tag, $index, $form ) { * * @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 int $index Number of $tag elements to find before inserting form. * @param string $form Form HTML to inject. * @return string */