diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 012e5a15..69fe0bf5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -192,7 +192,7 @@ jobs: - name: Start chromedriver run: | export DISPLAY=:99 - chromedriver --url-base=/wd/hub & + chromedriver --port=9515 --url-base=/wd/hub & sudo Xvfb -ac :99 -screen 0 1920x1080x24 > /dev/null 2>&1 & # optional # Write any secrets, such as API keys, to the .env.dist.testing file now. diff --git a/includes/class-wp-convertkit.php b/includes/class-wp-convertkit.php index 2566f2c6..d3f157e8 100644 --- a/includes/class-wp-convertkit.php +++ b/includes/class-wp-convertkit.php @@ -174,6 +174,7 @@ private function initialize_global() { $this->classes['pre_publish_action_broadcast_export'] = new ConvertKit_Pre_Publish_Action_Broadcast_Export(); $this->classes['broadcasts_exporter'] = new ConvertKit_Broadcasts_Exporter(); $this->classes['broadcasts_importer'] = new ConvertKit_Broadcasts_Importer(); + $this->classes['divi'] = new ConvertKit_Divi(); $this->classes['elementor'] = new ConvertKit_Elementor(); $this->classes['gutenberg'] = new ConvertKit_Gutenberg(); $this->classes['media_library'] = new ConvertKit_Media_Library(); diff --git a/includes/integrations/divi/class-convertkit-divi-extension.php b/includes/integrations/divi/class-convertkit-divi-extension.php new file mode 100644 index 00000000..0a3f08b9 --- /dev/null +++ b/includes/integrations/divi/class-convertkit-divi-extension.php @@ -0,0 +1,66 @@ +plugin_dir = CONVERTKIT_PLUGIN_PATH . '/includes/integrations/divi/'; + $this->plugin_dir_url = CONVERTKIT_PLUGIN_URL . 'includes/integrations/divi/'; + + // Store any JS data that can be accessed by builder-bundle.min.js using window.ConvertkitDiviBuilderData. + $this->_builder_js_data = convertkit_get_blocks(); + + // Call parent construct. + parent::__construct( $name, $args ); + + } +} + +new ConvertKit_Divi_Extension(); diff --git a/includes/integrations/divi/class-convertkit-divi-module-form.php b/includes/integrations/divi/class-convertkit-divi-module-form.php new file mode 100644 index 00000000..56a80e8c --- /dev/null +++ b/includes/integrations/divi/class-convertkit-divi-module-form.php @@ -0,0 +1,37 @@ +block_name, $blocks ) ) { + return; + } + + // Define the block and its name. + $this->block = $blocks[ $this->block_name ]; + $this->name = esc_html( $this->block['title'] ); + + } + + /** + * Defines the fields that can be configured for this Module + * + * @since 2.5.6 + */ + public function get_fields() { + + // Bail if no block. + if ( is_wp_error( $this->block ) ) { + return array(); + } + + // Bail if no fields. + if ( ! is_array( $this->block['fields'] ) ) { + return array(); + } + + // Build fields. + $fields = array(); + foreach ( $this->block['fields'] as $field_name => $field ) { + // Start building field definition. + $fields[ $field_name ] = array( + 'type' => $field['type'], + 'default' => $this->get_default_value( $field ), + 'description' => ( isset( $field['description'] ) ? $field['description'] : '' ), + 'label' => $field['label'], + 'toggle_slug' => 'main_content', + ); + + // Add/change field parameters depending on the field's type. + switch ( $field['type'] ) { + /** + * Number + */ + case 'number': + $fields[ $field_name ] = array_merge( + $fields[ $field_name ], + array( + 'type' => 'range', + 'range_settings' => array( + 'min' => $field['min'], + 'max' => $field['max'], + 'step' => $field['step'], + ), + 'unitless' => true, + ) + ); + break; + + /** + * Select + */ + case 'select': + $fields[ $field_name ]['options'] = $field['values']; + break; + + /** + * Toggle + */ + case 'toggle': + $fields[ $field_name ] = array_merge( + $fields[ $field_name ], + array( + 'type' => 'yes_no_button', + 'default' => ( $fields[ $field_name ]['default'] ? 'on' : 'off' ), + 'options' => array( + 'off' => __( 'No', 'convertkit' ), + 'on' => __( 'Yes', 'convertkit' ), + ), + ) + ); + break; + + } + } + + // Return. + return $fields; + + } + + /** + * Render the module. + * + * @since 2.5.6 + * + * @param array|string $unprocessed_props Unprocessed properties. + * @param array|string $content Content. + * @param string $render_slug Slug. + * @return string Block's output. + */ + public function render( $unprocessed_props, $content, $render_slug ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter + + // Render using Block class' render() function. + // Output is already escaped in render() function. + return WP_ConvertKit()->get_class( 'blocks_convertkit_' . $this->block_name )->render( $unprocessed_props ); // phpcs:ignore WordPress.Security.EscapeOutput + + } + + /** + * Returns the default value for the given field configuration. + * + * If the field's default value is an array, it's converted to a string, + * to prevent Divi builder timeout errors on the frontend. + * + * @since 2.5.6 + * + * @param array $field Field. + * @return string|int|object Default Value + */ + private function get_default_value( $field ) { + + // Return a blank string if the field doesn't specify a default value. + if ( ! array_key_exists( 'default_value', $field ) ) { + return ''; + } + + return $field['default_value']; + + } + +} diff --git a/includes/integrations/divi/class-convertkit-divi.php b/includes/integrations/divi/class-convertkit-divi.php new file mode 100644 index 00000000..a260d0e7 --- /dev/null +++ b/includes/integrations/divi/class-convertkit-divi.php @@ -0,0 +1,40 @@ +scrollTo('#submitdiv'); @@ -263,6 +262,20 @@ public function publishAndViewClassicEditorPage($I) // Wait for notice to display. $I->waitForElementVisible('.notice-success'); + } + + /** + * Publish a Page, Post or Custom Post Type initiated by the addClassicEditorPage() function, + * loading it on the frontend web site. + * + * @since 1.9.7.5 + * + * @param AcceptanceTester $I Acceptance Tester. + */ + public function publishAndViewClassicEditorPage($I) + { + // Publish Page. + $I->publishClassicEditorPage($I); // Load the Page on the frontend site. $I->click('.notice-success a'); diff --git a/tests/_support/Helper/Acceptance/WPGutenberg.php b/tests/_support/Helper/Acceptance/WPGutenberg.php index e9932087..8ebe85e0 100644 --- a/tests/_support/Helper/Acceptance/WPGutenberg.php +++ b/tests/_support/Helper/Acceptance/WPGutenberg.php @@ -363,7 +363,7 @@ function($I) { ); // Wait for confirmation that the Page published. - $I->waitForElementVisible('.post-publish-panel__postpublish-buttons a.components-button'); + $I->waitForElementVisible('.post-publish-panel__postpublish-buttons a.components-button', 30); // Return URL from 'View page' button. return $I->grabAttributeFrom('.post-publish-panel__postpublish-buttons a.components-button', 'href'); diff --git a/tests/acceptance/integrations/other/DiviFormCest.php b/tests/acceptance/integrations/other/DiviFormCest.php new file mode 100644 index 00000000..fc40c3f8 --- /dev/null +++ b/tests/acceptance/integrations/other/DiviFormCest.php @@ -0,0 +1,294 @@ +activateConvertKitPlugin($I); + $I->activateThirdPartyPlugin($I, 'divi-builder'); + + // Setup Plugin, without defining default Forms. + $I->setupConvertKitPluginNoDefaultForms($I); + $I->setupConvertKitPluginResources($I); + } + + /** + * Test the Form widget works when a valid Form is selected + * using Divi's backend editor. + * + * @since 2.5.6 + * + * @param AcceptanceTester $I Tester. + */ + public function testFormModuleInBackendEditor(AcceptanceTester $I) + { + // Activate Classic Editor Plugin. + $I->activateThirdPartyPlugin($I, 'classic-editor'); + + // Add a Page using the Classic Editor. + $I->addClassicEditorPage($I, 'page', 'ConvertKit: Page: Form: Divi: Backend Editor'); + + // Configure metabox's Form setting = None, ensuring we only test the block in Gutenberg. + $I->configureMetaboxSettings( + $I, + 'wp-convertkit-meta-box', + [ + 'form' => [ 'select2', 'None' ], + ] + ); + + // Scroll to Publish meta box, so its buttons are not hidden. + $I->scrollTo('#submitdiv'); + + // Wait for the Publish button to change its state from disabled (WordPress disables it for a moment when auto-saving). + $I->waitForElementVisible('input#publish:not(:disabled)'); + + // Click the Publish button twice, because Divi is flaky at best. + $I->click('input#publish'); + $I->wait(2); + $I->click('input#publish'); + + // Wait for notice to display. + $I->waitForElementVisible('.notice-success'); + + // Remove transient set by Divi that would show the welcome modal. + $I->dontHaveTransientInDatabase('et_builder_show_bfb_welcome_modal'); + + // Click Divi Builder button. + $I->click('#et_pb_toggle_builder'); + + // Dismiss modal. + $I->waitForElementVisible('.et-core-modal-action-dont-restore'); + $I->click('.et-core-modal-action-dont-restore'); + + // Click Build from scratch button. + $I->waitForElementVisible('.et-fb-page-creation-card-build_from_scratch'); + $I->click('Start Building', '.et-fb-page-creation-card-build_from_scratch'); + + // Insert row. + $I->waitForElementVisible('li[data-layout="4_4"]'); + $I->click('li[data-layout="4_4"]'); + + // Search for module. + $I->waitForElementVisible('input[name="filterByTitle"]'); + $I->fillField('filterByTitle', 'ConvertKit Form'); + + // Insert module. + $I->waitForElementVisible('li.convertkit_form'); + $I->click('li.convertkit_form'); + + // Select Form. + $I->waitForElementVisible('#et-fb-form'); + $I->click('#et-fb-form'); + $I->click('li[data-value="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', '#et-fb-form'); + + // Save module. + $I->click('button[data-tip="Save Changes"]'); + + // Update page. + $I->click('Update'); + + // Load the Page on the frontend site. + $I->waitForElementVisible('.notice-success'); + $I->click('.notice-success a'); + + // Wait for frontend web site to load. + $I->waitForElementVisible('body'); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Confirm that one ConvertKit Form is output in the DOM. + // This confirms that there is only one script on the page for this form, which renders the form. + $I->seeNumberOfElementsInDOM('form[data-sv-form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', 1); + + // Deactivate Classic Editor. + $I->deactivateThirdPartyPlugin($I, 'classic-editor'); + } + + /** + * Test the Form widget works when a valid Form is selected + * using Divi's backend editor. + * + * @since 2.5.6 + * + * @param AcceptanceTester $I Tester. + */ + public function testFormModuleInFrontendEditor(AcceptanceTester $I) + { + // Add a Page using the Gutenberg editor. + $I->addGutenbergPage($I, 'page', 'ConvertKit: Page: Divi: Frontend'); + + // Configure metabox's Form setting = None, ensuring we only test the block in Gutenberg. + $I->configureMetaboxSettings( + $I, + 'wp-convertkit-meta-box', + [ + 'form' => [ 'select2', 'None' ], + ] + ); + + // Publish Page. + $url = $I->publishGutenbergPage($I); + + // Click Divi Builder button. + $I->click('Use Divi Builder'); + + // Reload page to dismiss modal. + $I->wait(5); + $I->amOnUrl($url . '?et_fb=1&PageSpeed=off'); + + // Click Build from scratch button. + $I->waitForElementVisible('.et-fb-page-creation-card-build_from_scratch', 30); + $I->click('Start Building', '.et-fb-page-creation-card-build_from_scratch'); + + // Insert row. + $I->waitForElementVisible('li[data-layout="4_4"]'); + $I->click('li[data-layout="4_4"]'); + + // Search for module. + $I->waitForElementVisible('input[name="filterByTitle"]'); + $I->fillField('filterByTitle', 'ConvertKit Form'); + + // Insert module. + $I->waitForElementVisible('li.convertkit_form'); + $I->click('li.convertkit_form'); + + // Select Form. + $I->waitForElementVisible('#et-fb-form'); + $I->click('#et-fb-form'); + $I->click('li[data-value="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', '#et-fb-form'); + + // Save module. + $I->click('button[data-tip="Save Changes"]'); + + // Save page. + $I->click('.et-fb-page-settings-bar__toggle-button'); + $I->waitForElementVisible('button.et-fb-button--publish'); + $I->click('button.et-fb-button--publish'); + $I->wait(3); + + // Load page without Divi frontend builder. + $I->amOnUrl($url); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Confirm that one ConvertKit Form is output in the DOM. + // This confirms that there is only one script on the page for this form, which renders the form. + $I->seeNumberOfElementsInDOM('form[data-sv-form="' . $_ENV['CONVERTKIT_API_FORM_ID'] . '"]', 1); + } + + /** + * Test the Form module works when a valid Legacy Form is selected. + * + * @since 2.5.6 + * + * @param AcceptanceTester $I Tester. + */ + public function testFormModuleWithValidLegacyFormParameter(AcceptanceTester $I) + { + // Create Page with Form module in Divi. + $pageID = $this->_createPageWithFormModule($I, 'ConvertKit: Legacy Form: Divi Module: Valid Form Param', $_ENV['CONVERTKIT_API_LEGACY_FORM_ID']); + + // Load Page. + $I->amOnPage('?p=' . $pageID); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Confirm that the ConvertKit Form is displayed. + $I->seeInSource('
'); + } + + /** + * Test the Form module works when no Form is selected. + * + * @since 2.5.6 + * + * @param AcceptanceTester $I Tester. + */ + public function testFormModuleWithNoFormParameter(AcceptanceTester $I) + { + // Create Page with Form module in Divi. + $pageID = $this->_createPageWithFormModule($I, 'ConvertKit: Page: Form: Divi Module: No Form Param', ''); + + // Load Page. + $I->amOnPage('?p=' . $pageID); + + // Check that no PHP warnings or notices were output. + $I->checkNoWarningsAndNoticesOnScreen($I); + + // Confirm that no ConvertKit Form is displayed. + $I->dontSeeElementInDOM('form[data-sv-form]'); + } + + /** + * Create a Page in the database comprising of Divi Page Builder data + * containing a ConvertKit Form module. + * + * @since 2.5.6 + * + * @param AcceptanceTester $I Tester. + * @param string $title Page Title. + * @param int $formID ConvertKit Form ID. + * @return int Page ID + */ + private function _createPageWithFormModule(AcceptanceTester $I, $title, $formID) + { + return $I->havePostInDatabase( + [ + 'post_title' => $title, + 'post_type' => 'page', + 'post_status' => 'publish', + 'post_content' => '[et_pb_section fb_built="1" _builder_version="4.27.0" _module_preset="default" global_colors_info="{}"] + [et_pb_row _builder_version="4.27.0" _module_preset="default"] + [et_pb_column _builder_version="4.27.0" _module_preset="default" type="4_4"] + [convertkit_form _builder_version="4.27.0" _module_preset="default" form="' . $formID . '" hover_enabled="0" sticky_enabled="0"][/convertkit_form] + [/et_pb_column] + [/et_pb_row] + [/et_pb_section]', + 'meta_input' => [ + // Enable Divi Builder. + '_et_pb_use_builder' => 'on', + '_et_pb_built_for_post_type' => 'page', + + // Configure ConvertKit Plugin to not display a default Form, + // as we are testing for the Form in Elementor. + '_wp_convertkit_post_meta' => [ + 'form' => '0', + 'landing_page' => '', + 'tag' => '', + ], + ], + ] + ); + } + + /** + * Deactivate and reset Plugin(s) after each test, if the test passes. + * We don't use _after, as this would provide a screenshot of the Plugin + * deactivation and not the true test error. + * + * @since 2.5.6 + * + * @param AcceptanceTester $I Tester. + */ + public function _passed(AcceptanceTester $I) + { + $I->deactivateThirdPartyPlugin($I, 'divi-builder'); + $I->deactivateConvertKitPlugin($I); + $I->resetConvertKitPlugin($I); + } +} diff --git a/wp-convertkit.php b/wp-convertkit.php index 28c8c38c..40b812dd 100644 --- a/wp-convertkit.php +++ b/wp-convertkit.php @@ -94,6 +94,9 @@ require_once CONVERTKIT_PLUGIN_PATH . '/includes/integrations/contactform7/class-convertkit-contactform7.php'; require_once CONVERTKIT_PLUGIN_PATH . '/includes/integrations/contactform7/class-convertkit-contactform7-settings.php'; +// Divi Integration. +require_once CONVERTKIT_PLUGIN_PATH . '/includes/integrations/divi/class-convertkit-divi.php'; + // Elementor Integration. require_once CONVERTKIT_PLUGIN_PATH . '/includes/integrations/elementor/class-convertkit-elementor.php';