From 9fd2a44471d691943046c822754083096275d6dd Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 19 May 2025 16:54:28 +0530 Subject: [PATCH 1/8] Fixed phone number and required field validations. --- includes/class-mailchimp-form-submission.php | 80 ++++++++++++++++++-- includes/mailchimp-deprecated-functions.php | 33 ++++++++ mailchimp.php | 61 +-------------- 3 files changed, 109 insertions(+), 65 deletions(-) diff --git a/includes/class-mailchimp-form-submission.php b/includes/class-mailchimp-form-submission.php index 96fd7078..b6f6a3e5 100644 --- a/includes/class-mailchimp-form-submission.php +++ b/includes/class-mailchimp-form-submission.php @@ -173,6 +173,76 @@ public function handle_form_submission() { return $message; } + /** + * Validate phone + * + * @param array $opt_val Option value. + * @param array $data Data. + * @return string|WP_Error Option value or error. + */ + public function validate_phone( $opt_val, $data ) { + // This filters out all 'falsy' elements + $opt_val = array_filter( $opt_val ); + // If they weren't all empty + if ( empty( $opt_val ) ) { + return ''; + } + + // Trim the phone number + $opt_val = array_map( + function ( $ele ) { + return preg_replace( '/\s+/', '', trim( $ele ) ); + }, + $opt_val + ); + + $opt_val = implode( '-', $opt_val ); + if ( strlen( $opt_val ) < 12 ) { + // translators: %s: field name + $message = sprintf( esc_html__( '%s should be 10 digits long.', 'mailchimp' ), esc_html( $data['name'] ) ); + $error = new WP_Error( 'mc_phone_validation', $message ); + return $error; + } + + if ( ! preg_match( '/^[0-9]{3}-[0-9]{3}-[0-9]{4}$/', $opt_val ) ) { + /* translators: %s: field name */ + $message = sprintf( esc_html__( '%s must consist of only numbers', 'mailchimp' ), esc_html( $data['name'] ) ); + $error = new WP_Error( 'mc_phone_validation', $message ); + return $error; + } + + return $opt_val; + } + + /** + * Validate address + * + * @param array $opt_val Option value + * @param array $data Data + * @return mixed + */ + public function validate_address( $opt_val, $data ) { + if ( true === (bool) $data['required'] ) { + if ( empty( $opt_val['addr1'] ) || empty( $opt_val['city'] ) ) { + /* translators: %s: field name */ + $message = sprintf( esc_html__( '%s: Please enter a complete address.', 'mailchimp' ), esc_html( $data['name'] ) ); + $error = new WP_Error( 'invalid_address_merge', $message ); + return $error; + } + } elseif ( empty( $opt_val['addr1'] ) || empty( $opt_val['city'] ) ) { + return false; + } + + $merge = new stdClass(); + $merge->addr1 = $opt_val['addr1']; + $merge->addr2 = $opt_val['addr2']; + $merge->city = $opt_val['city']; + $merge->state = $opt_val['state']; + $merge->zip = $opt_val['zip']; + $merge->country = $opt_val['country']; + return $merge; + } + /** * Prepare the merge fields body for the API request. * @@ -187,7 +257,7 @@ public function prepare_merge_fields_body( $merge_fields ) { $opt = 'mc_mv_' . $tag; // Skip if the field is not required and not submitted. - if ( 'Y' !== $merge_field['required'] && ! isset( $_POST[ $opt ] ) ) { + if ( true !== (bool) $merge_field['required'] && ! isset( $_POST[ $opt ] ) ) { continue; } @@ -206,7 +276,7 @@ public function prepare_merge_fields_body( $merge_fields ) { isset( $merge_field['options']['phone_format'] ) && 'US' === $merge_field['options']['phone_format'] ) { - $opt_val = mailchimp_sf_merge_validate_phone( $opt_val, $merge_field ); + $opt_val = $this->validate_phone( $opt_val, $merge_field ); if ( is_wp_error( $opt_val ) ) { return $opt_val; } @@ -221,7 +291,7 @@ public function prepare_merge_fields_body( $merge_fields ) { */ case 'address': if ( is_array( $opt_val ) ) { - $validate = mailchimp_sf_merge_validate_address( $opt_val, $merge_field ); + $validate = $this->validate_address( $opt_val, $merge_field ); if ( is_wp_error( $validate ) ) { return $validate; } @@ -254,9 +324,9 @@ public function prepare_merge_fields_body( $merge_fields ) { /** * Required fields * - * If the field is required and empty, return an error + * If the field is required and empty, +return an error */ - if ( 'Y' === $merge_field['required'] && trim( $opt_val ) === '' ) { + if ( true === (bool) $merge_field['required'] && empty( $opt_val ) ) { /* translators: %s: field name */ $message = sprintf( esc_html__( 'You must fill in %s.', 'mailchimp' ), esc_html( $merge_field['name'] ) ); $error = new WP_Error( 'missing_required_field', $message ); diff --git a/includes/mailchimp-deprecated-functions.php b/includes/mailchimp-deprecated-functions.php index 00ac791c..b30e4a90 100644 --- a/includes/mailchimp-deprecated-functions.php +++ b/includes/mailchimp-deprecated-functions.php @@ -113,3 +113,36 @@ function mailchimp_sf_merge_remove_empty( $merge ) { $form_submission = new Mailchimp_Form_Submission(); return $form_submission->remove_empty_merge_fields( $merge ); } + + +/** + * Validate phone + * + * @deprecated x.x.x + * + * @param array $opt_val Option value. + * @param array $data Data. + * @return string|WP_Error Option value or error. + */ +function mailchimp_sf_merge_validate_phone( $opt_val, $data ): string|WP_Error { + _deprecated_function( __FUNCTION__, 'x.x.x', 'Mailchimp_Form_Submission::validate_phone()' ); + + $form_submission = new Mailchimp_Form_Submission(); + return $form_submission->validate_phone( $opt_val, $data ); +} + +/** + * Validate address + * + * @deprecated x.x.x + * + * @param array $opt_val Option value. + * @param array $data Data. + * @return mixed + */ +function mailchimp_sf_merge_validate_address( $opt_val, $data ) { + _deprecated_function( __FUNCTION__, 'x.x.x', 'Mailchimp_Form_Submission::validate_address()' ); + + $form_submission = new Mailchimp_Form_Submission(); + return $form_submission->validate_address( $opt_val, $data ); +} diff --git a/mailchimp.php b/mailchimp.php index 73f8b6c9..0fcb1bce 100644 --- a/mailchimp.php +++ b/mailchimp.php @@ -544,7 +544,7 @@ function mailchimp_sf_save_general_form_settings() { if ( is_array( $mv ) ) { foreach ( $mv as $mv_var ) { $opt = 'mc_mv_' . $mv_var['tag']; - if ( isset( $_POST[ $opt ] ) || 'Y' === $mv_var['required'] ) { + if ( isset( $_POST[ $opt ] ) || true === (bool) $mv_var['required'] ) { update_option( $opt, 'on' ); } else { update_option( $opt, 'off' ); @@ -830,65 +830,6 @@ function mailchimp_sf_check_status( $endpoint ) { return $subscriber['status']; } -/** - * Validate phone - * - * @param array $opt_val Option value - * @param array $data Data - * @return void - */ -function mailchimp_sf_merge_validate_phone( $opt_val, $data ) { - // This filters out all 'falsey' elements - $opt_val = array_filter( $opt_val ); - // If they weren't all empty - if ( ! $opt_val ) { - return; - } - - $opt_val = implode( '-', $opt_val ); - if ( strlen( $opt_val ) < 12 ) { - $opt_val = ''; - } - - if ( ! preg_match( '/[0-9]{0,3}-[0-9]{0,3}-[0-9]{0,4}/A', $opt_val ) ) { - /* translators: %s: field name */ - $message = sprintf( esc_html__( '%s must consist of only numbers', 'mailchimp' ), esc_html( $data['name'] ) ); - $error = new WP_Error( 'mc_phone_validation', $message ); - return $error; - } - - return $opt_val; -} - -/** - * Validate address - * - * @param array $opt_val Option value - * @param array $data Data - * @return mixed - */ -function mailchimp_sf_merge_validate_address( $opt_val, $data ) { - if ( 'Y' === $data['required'] ) { - if ( empty( $opt_val['addr1'] ) || empty( $opt_val['city'] ) ) { - /* translators: %s: field name */ - $message = sprintf( esc_html__( 'You must fill in %s.', 'mailchimp' ), esc_html( $data['name'] ) ); - $error = new WP_Error( 'invalid_address_merge', $message ); - return $error; - } - } elseif ( empty( $opt_val['addr1'] ) || empty( $opt_val['city'] ) ) { - return false; - } - - $merge = new stdClass(); - $merge->addr1 = $opt_val['addr1']; - $merge->addr2 = $opt_val['addr2']; - $merge->city = $opt_val['city']; - $merge->state = $opt_val['state']; - $merge->zip = $opt_val['zip']; - $merge->country = $opt_val['country']; - return $merge; -} - /** * Verify key * From ed9dc29de61bdf47a845c4c2b62a067cad38218b Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 19 May 2025 17:32:24 +0530 Subject: [PATCH 2/8] Add email field validation. --- includes/class-mailchimp-form-submission.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/includes/class-mailchimp-form-submission.php b/includes/class-mailchimp-form-submission.php index b6f6a3e5..63cae721 100644 --- a/includes/class-mailchimp-form-submission.php +++ b/includes/class-mailchimp-form-submission.php @@ -571,6 +571,16 @@ protected function validate_form_submission() { return new WP_Error( 'spam', $spam_message ); } + // Early return if the email is not set + if ( empty( $_POST['mc_mv_EMAIL'] ) ) { + return new WP_Error( 'email_required', esc_html__( 'Please enter your email address.', 'mailchimp' ) ); + } + + // Check if the email is valid + if ( ! is_email( sanitize_email( wp_unslash( $_POST['mc_mv_EMAIL'] ) ) ) ) { + return new WP_Error( 'invalid_email', esc_html__( 'Please enter a valid email address.', 'mailchimp' ) ); + } + /** * Filter to allow for custom validation of the form submission. * From ff604dd10283607af7e07c2f5f1cde53b13624a9 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Mon, 19 May 2025 19:33:43 +0530 Subject: [PATCH 3/8] E2E: E2E tests updates. --- includes/class-mailchimp-form-submission.php | 2 +- tests/cypress/e2e/block.test.js | 35 +++++++------- tests/cypress/e2e/settings/settings.test.js | 4 +- .../e2e/submission/js-submission.test.js | 10 +--- .../cypress/e2e/submission/subscribe.test.js | 2 +- tests/cypress/e2e/validation/email.test.js | 9 ++-- tests/cypress/e2e/validation/us-phone.test.js | 47 +++++++++---------- .../validate-required-fields.test.js | 29 +++++++----- 8 files changed, 67 insertions(+), 71 deletions(-) diff --git a/includes/class-mailchimp-form-submission.php b/includes/class-mailchimp-form-submission.php index 63cae721..a3eb3934 100644 --- a/includes/class-mailchimp-form-submission.php +++ b/includes/class-mailchimp-form-submission.php @@ -197,7 +197,7 @@ function ( $ele ) { ); $opt_val = implode( '-', $opt_val ); - if ( strlen( $opt_val ) < 12 ) { + if ( strlen( $opt_val ) !== 12 ) { // translators: %s: field name $message = sprintf( esc_html__( '%s should be 10 digits long.', 'mailchimp' ), esc_html( $data['name'] ) ); $error = new WP_Error( 'mc_phone_validation', $message ); diff --git a/tests/cypress/e2e/block.test.js b/tests/cypress/e2e/block.test.js index a492046b..630c7be3 100644 --- a/tests/cypress/e2e/block.test.js +++ b/tests/cypress/e2e/block.test.js @@ -28,7 +28,7 @@ describe('Block Tests', () => { cy.get('#mc_signup_submit').should('exist'); cy.get('#mc_signup_submit').click(); cy.get('.mc_error_msg').should('exist'); - cy.get('.mc_error_msg').contains('Email Address: This value should not be blank.'); + cy.get('.mc_error_msg').contains('Please enter your email address.'); } }); }); @@ -48,6 +48,7 @@ describe('Block Tests', () => { .type(subHeader); cy.getBlockEditor().find('button[aria-label="Enter button text."]').clear().type(button); cy.get('button.editor-post-publish-button').click(); + cy.wait(1000); // Verify cy.visit(`/?p=${postId}`); @@ -67,6 +68,7 @@ describe('Block Tests', () => { .find('button[aria-label="Move down"]') .click(); cy.get('button.editor-post-publish-button').click(); + cy.wait(1000); // Verify order of fields cy.visit(`/?p=${postId}`); @@ -83,6 +85,7 @@ describe('Block Tests', () => { .find('button[aria-label="Move up"]') .click(); cy.get('button.editor-post-publish-button').click(); + cy.wait(1000); // Verify order of fields cy.visit(`/?p=${postId}`); @@ -100,7 +103,7 @@ describe('Block Tests', () => { cy.get('.block-editor-block-toolbar__slot').find('button[aria-label="Visibility"]').click(); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); // Verify cy.visit(`/?p=${postId}`); @@ -116,7 +119,7 @@ describe('Block Tests', () => { .find('button[aria-label="Visibility"].is-pressed') .click(); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); // Verify cy.visit(`/?p=${postId}`); @@ -134,7 +137,7 @@ describe('Block Tests', () => { .find('button[aria-label="Visibility"].is-pressed') .click(); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); // Verify cy.visit(`/?p=${postId}`); @@ -149,7 +152,7 @@ describe('Block Tests', () => { cy.get('.block-editor-block-toolbar__slot').find('button[aria-label="Visibility"]').click(); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); // Verify cy.visit(`/?p=${postId}`); @@ -164,7 +167,7 @@ describe('Block Tests', () => { cy.getBlockEditor().find('label[for="EMAIL"] label').clear().type(emailLabel); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); // Verify cy.visit(`/?p=${postId}`); @@ -179,7 +182,7 @@ describe('Block Tests', () => { cy.openDocumentSettingsPanel('Form Settings', 'Block'); cy.get('.mailchimp-unsubscribe-link input.components-form-toggle__input').first().check(); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); // Verify cy.visit(`/?p=${postId}`); @@ -191,7 +194,7 @@ describe('Block Tests', () => { cy.openDocumentSettingsPanel('Form Settings', 'Block'); cy.get('.mailchimp-unsubscribe-link input.components-form-toggle__input').first().uncheck(); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); // Verify cy.visit(`/?p=${postId}`); @@ -203,13 +206,13 @@ describe('Block Tests', () => { cy.visit(`/wp-admin/post.php?post=${postId}&action=edit`); cy.getBlockEditor().find('h2[aria-label="Enter a header (optional)"]').click(); cy.openDocumentSettingsPanel('Settings', 'Block'); - cy.get('.mailchimp-list-select select').select('Alternate 10up Audience'); + cy.get('.mailchimp-list-select select').select('Project Lotus'); cy.wait(2000); cy.getBlockEditor().find('label[for="EMAIL"] label').contains('Email Address'); cy.getBlockEditor().find('label[for="MMERGE9"]').should('not.exist'); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); // Verify cy.visit(`/?p=${postId}`); @@ -225,7 +228,7 @@ describe('Block Tests', () => { cy.wait(2000); cy.getBlockEditor().find('label[for="MMERGE9"]').should('exist'); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); }); it('[Backward Compatibility] Admin can see settings for the existing old block', () => { @@ -245,7 +248,7 @@ describe('Block Tests', () => { .clear() .type(header); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); // Verify cy.visit(`/?p=${oldBlockPostId}`); @@ -262,7 +265,7 @@ describe('Block Tests', () => { .first() .check(); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); // Verify cy.visit(`/?p=${postId}`); @@ -281,7 +284,7 @@ describe('Block Tests', () => { cy.openDocumentSettingsPanel('Form Settings', 'Block'); cy.get('.mailchimp-double-opt-in input.components-form-toggle__input').first().check(); cy.get('button.editor-post-publish-button').click(); - cy.wait(500); + cy.wait(1000); }); it('Form data should persist if validation fails', () => { @@ -295,7 +298,7 @@ describe('Block Tests', () => { cy.get('#mc_signup_submit').should('exist'); cy.get('#mc_signup_submit').click(); cy.get('.mc_error_msg').should('exist'); - cy.get('.mc_error_msg').contains('Email Address: This value should not be blank.'); + cy.get('.mc_error_msg').contains('Please enter your email address.'); cy.get('#mc_mv_FNAME').should('have.value', firstName); cy.get('#mc_mv_LNAME').should('have.value', lastName); }); @@ -320,7 +323,7 @@ describe('Block Tests', () => { cy.get('#mc_signup_submit').should('exist'); cy.get('#mc_signup_submit').click(); cy.get('.mc_error_msg').should('exist'); - cy.get('.mc_error_msg').contains('Email Address: This value should not be blank.'); + cy.get('.mc_error_msg').contains('Please enter your email address.'); }); // TODO: Add tests for the Double Opt-in and Update existing subscribers settings. diff --git a/tests/cypress/e2e/settings/settings.test.js b/tests/cypress/e2e/settings/settings.test.js index adb63701..ac01a4c7 100644 --- a/tests/cypress/e2e/settings/settings.test.js +++ b/tests/cypress/e2e/settings/settings.test.js @@ -170,7 +170,7 @@ describe('Admin can update plugin settings', () => { cy.get('#mc_signup_submit').should('exist'); cy.get('#mc_signup_submit').click(); cy.get('.mc_error_msg').should('exist'); - cy.get('.mc_error_msg').contains('Email Address: This value should not be blank.'); + cy.get('.mc_error_msg').contains('Please enter your email address.'); cy.get('#mc_mv_FNAME').should('have.value', firstName); cy.get('#mc_mv_LNAME').should('have.value', lastName); }); @@ -228,7 +228,7 @@ describe('Admin can update plugin settings', () => { cy.get('#mc_signup_submit').should('exist'); cy.get('#mc_signup_submit').click(); cy.get('.mc_error_msg').should('exist'); - cy.get('.mc_error_msg').contains('Email Address: This value should not be blank.'); + cy.get('.mc_error_msg').contains('Please enter your email address.'); }); }); diff --git a/tests/cypress/e2e/submission/js-submission.test.js b/tests/cypress/e2e/submission/js-submission.test.js index 5a43d455..f82f99a3 100644 --- a/tests/cypress/e2e/submission/js-submission.test.js +++ b/tests/cypress/e2e/submission/js-submission.test.js @@ -47,7 +47,7 @@ describe('JavaScript submission', () => { }); it('Disables the submit button before attempting submission', () => { - submitEmail('invalidemail@--'); // Submit blank email + submitEmail('invalidemail@test.com'); // Submit blank email // Step 4: Assert that the submit button is disabled after submitting the form cy.get('#mc_signup_submit').should('be.disabled'); @@ -81,13 +81,7 @@ describe('JavaScript submission', () => { cy.deleteContactFromList(email); }); - // TODO: This is a bug and is currently broken - it.skip('Persist form data on Mailchimp API validation failure', () => { - // Write test... - }); - - // TODO: BUG: Single opt-in is currently broken, but a fix is scheduled for 1.7.0 - it.skip('Success submission with JS support adds email to Mailchimp account as contact', () => { + it('Success submission with JS support adds email to Mailchimp account as contact', () => { const email = generateRandomEmail('javascript-submission-verify-submission'); submitEmail(email); diff --git a/tests/cypress/e2e/submission/subscribe.test.js b/tests/cypress/e2e/submission/subscribe.test.js index 910b8af5..c5d3076d 100644 --- a/tests/cypress/e2e/submission/subscribe.test.js +++ b/tests/cypress/e2e/submission/subscribe.test.js @@ -22,7 +22,7 @@ describe('Subscribe actions', () => { // Step 4: Test error handling cy.get('#mc_signup_submit').click(); cy.get('.mc_error_msg').should('exist'); - cy.get('.mc_error_msg').contains('Email Address: This value should not be blank.'); + cy.get('.mc_error_msg').contains('Please enter your email address.'); // Step 5: Test that the form can be submitted const email = generateRandomEmail('shortcode-signup-test'); diff --git a/tests/cypress/e2e/validation/email.test.js b/tests/cypress/e2e/validation/email.test.js index 7d5246a8..a973afce 100644 --- a/tests/cypress/e2e/validation/email.test.js +++ b/tests/cypress/e2e/validation/email.test.js @@ -41,7 +41,7 @@ describe('General merge field validation', () => { // Email assertions cy.get('#mc_mv_EMAIL').clear(); // No email cy.submitFormAndVerifyError(); - cy.get('.mc_error_msg').contains('Email Address: This value should not be blank.'); + cy.get('.mc_error_msg').contains('Please enter your email address.'); cy.get('#mc_mv_EMAIL').clear().type('user@'); // Missing domain cy.submitFormAndVerifyError(); @@ -71,10 +71,9 @@ describe('General merge field validation', () => { cy.submitFormAndVerifyError(); cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); - // TODO: Mailchimp accepts this. Is this a bug? - // cy.get('#mc_mv_EMAIL').clear().type('user@example-.com'); // Domain ending with dash - // cy.submitFormAndVerifyError(); - // cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); + cy.get('#mc_mv_EMAIL').clear().type('user@example-.com'); // Domain ending with dash + cy.submitFormAndVerifyError(); + cy.get('.mc_error_msg').contains(invalidEmailErrorRegex); cy.get('#mc_mv_EMAIL').clear().type('"user@example.com'); // Unclosed quoted string cy.submitFormAndVerifyError(); diff --git a/tests/cypress/e2e/validation/us-phone.test.js b/tests/cypress/e2e/validation/us-phone.test.js index a523a7fc..68ab4c53 100644 --- a/tests/cypress/e2e/validation/us-phone.test.js +++ b/tests/cypress/e2e/validation/us-phone.test.js @@ -2,14 +2,9 @@ import { generateRandomEmail } from '../../support/functions/utility'; /** - * Test Suite for Multi-Input Phone Number Validation - * Handles both JavaScript-enabled and disabled scenarios for length and format validation. + * Test for Phone Number Validation */ -// TODO: BUG: Skipping for now because when a US phone number is selected in the Mailchimp account, but -// not present on the webform there will always be a fatal error. There is a fix pending for 1.7.0. -// TODO: Skipping for now because the Mailchimp API does not allow changing the format for a phone merge -// field to the US style -describe.skip('US Multi-Input Phone Number Validation', () => { +describe('US Multi-Input Phone Number Validation', () => { let blockPostPostURL; const validPhones = [ @@ -22,7 +17,7 @@ describe.skip('US Multi-Input Phone Number Validation', () => { ]; const tooShortPhones = [ { area: '12', detail1: '456', detail2: '789' }, - { area: '', detail1: '45', detail2: '7890' }, + { area: '1', detail1: '45', detail2: '7890' }, ]; const tooLongPhones = [ { area: '1234', detail1: '567', detail2: '890' }, @@ -37,25 +32,25 @@ describe.skip('US Multi-Input Phone Number Validation', () => { ({ blockPostPostURL } = urls); }); - // cy.getListId('10up').then((listId) => { - // cy.updateMergeFieldByTag(listId, 'PHONE', { - // required: true, - // options: { phone_format: 'US' }, - // }).then(() => { - // cy.selectList('10up'); - // }); - // }); + cy.getListId('10up').then((listId) => { + cy.updateMergeFieldByTag(listId, 'PHONE', { + required: true, + options: { phone_format: 'US' }, + }).then(() => { + cy.selectList('10up'); + }); + }); }); after(() => { - // cy.getListId('10up').then((listId) => { - // cy.updateMergeFieldByTag(listId, 'PHONE', { - // required: false, - // options: { phone_format: 'none' }, - // }).then(() => { - // cy.selectList('10up'); - // }); - // }); + cy.getListId('10up').then((listId) => { + cy.updateMergeFieldByTag(listId, 'PHONE', { + required: false, + options: { phone_format: 'none' }, + }).then(() => { + cy.selectList('10up'); + }); + }); }); function fillPhoneInputs(phone) { @@ -98,7 +93,7 @@ describe.skip('US Multi-Input Phone Number Validation', () => { cy.get('#mc_mv_EMAIL').type(email); fillPhoneInputs(phone); cy.submitFormAndVerifyError(); - cy.get('.mc_error_msg').contains('Phone number is too short'); + cy.get('.mc_error_msg').contains('should be 10 digits long'); }); tooLongPhones.forEach((phone) => { @@ -106,7 +101,7 @@ describe.skip('US Multi-Input Phone Number Validation', () => { cy.get('#mc_mv_EMAIL').type(email); fillPhoneInputs(phone); cy.submitFormAndVerifyError(); - cy.get('.mc_error_msg').contains('Phone number is too long'); + cy.get('.mc_error_msg').contains('should be 10 digits long'); }); }); }); diff --git a/tests/cypress/e2e/validation/validate-required-fields.test.js b/tests/cypress/e2e/validation/validate-required-fields.test.js index 9bbff306..eb45aa4f 100644 --- a/tests/cypress/e2e/validation/validate-required-fields.test.js +++ b/tests/cypress/e2e/validation/validate-required-fields.test.js @@ -7,21 +7,29 @@ describe('Validate required fields', () => { // (almost) the same in the WP admin as on the FE const requiredFields = [ - { selector: '#mc_mv_FNAME', errorMessage: 'First Name:', input: 'Test' }, - { selector: '#mc_mv_LNAME', errorMessage: 'Last Name:', input: 'User' }, + { selector: '#mc_mv_FNAME', errorMessage: 'You must fill in First Name', input: 'Test' }, + { selector: '#mc_mv_LNAME', errorMessage: 'You must fill in Last Name', input: 'User' }, { selector: '#mc_mv_ADDRESS-addr1', errorMessage: 'Address:', input: '123 Fake St.' }, // Address has sub fields on the FE form { selector: '#mc_mv_ADDRESS-city', errorMessage: 'Address:', input: 'Nashville' }, // Address has sub fields on the FE form { selector: '#mc_mv_ADDRESS-state', errorMessage: 'Address:', input: 'TN' }, // Address has sub fields on the FE form { selector: '#mc_mv_ADDRESS-zip', errorMessage: 'Address:', input: '12345' }, // Address has sub fields on the FE form - { selector: '#mc_mv_BIRTHDAY', errorMessage: 'Birthday:', input: '01/10' }, - { selector: '#mc_mv_COMPANY', errorMessage: 'Company:', input: '10up' }, - { selector: '#mc_mv_PHONE', errorMessage: 'Phone Number:', input: '555-555-5555' }, - { selector: '#mc_mv_MMERGE8', errorMessage: 'Date:', input: '01/01/2030' }, - { selector: '#mc_mv_MMERGE9', errorMessage: 'Zip Code:', input: '12345' }, - { selector: '#mc_mv_MMERGE10', errorMessage: 'Website:', input: 'https://10up.com' }, + { selector: '#mc_mv_BIRTHDAY', errorMessage: 'You must fill in Birthday', input: '01/10' }, + { selector: '#mc_mv_COMPANY', errorMessage: 'You must fill in Company', input: '10up' }, + { + selector: '#mc_mv_PHONE', + errorMessage: 'You must fill in Phone Number.', + input: '555-555-5555', + }, + { selector: '#mc_mv_MMERGE8', errorMessage: 'You must fill in Date.', input: '01/01/2030' }, + { selector: '#mc_mv_MMERGE9', errorMessage: 'You must fill in Zip Code.', input: '12345' }, + { + selector: '#mc_mv_MMERGE10', + errorMessage: 'You must fill in Website.', + input: 'https://10up.com', + }, { selector: '#mc_mv_MMERGE11', - errorMessage: 'Image:', + errorMessage: 'You must fill in Image.', input: 'https://10up.com/wp-content/themes/10up-sept2016/assets/img/icon-strategy.png', }, ]; @@ -86,8 +94,6 @@ describe('Validate required fields', () => { cy.toggleMergeFields('uncheck'); }); - // TODO: Validation errors clear the entire form. We should fix this. - // We could also significantly reduce the time this test takes by fixing this bug. function fillOutAllFields() { cy.get('#mc_mv_EMAIL').clear().type(email); // Email is always required @@ -114,7 +120,6 @@ describe('Validate required fields', () => { }); } - // TODO: Test just takes too long to run it('ensures that a required field can not be empty', () => { cy.visit(blockPostPostURL); From bbe3258143ae399107cd78ace52d80b5731a135e Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Thu, 22 May 2025 11:23:10 +0530 Subject: [PATCH 4/8] E2E: Update list name. --- tests/cypress/e2e/block.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/cypress/e2e/block.test.js b/tests/cypress/e2e/block.test.js index 630c7be3..b8a556f1 100644 --- a/tests/cypress/e2e/block.test.js +++ b/tests/cypress/e2e/block.test.js @@ -206,7 +206,7 @@ describe('Block Tests', () => { cy.visit(`/wp-admin/post.php?post=${postId}&action=edit`); cy.getBlockEditor().find('h2[aria-label="Enter a header (optional)"]').click(); cy.openDocumentSettingsPanel('Settings', 'Block'); - cy.get('.mailchimp-list-select select').select('Project Lotus'); + cy.get('.mailchimp-list-select select').select('Alternate 10up Audience'); cy.wait(2000); cy.getBlockEditor().find('label[for="EMAIL"] label').contains('Email Address'); cy.getBlockEditor().find('label[for="MMERGE9"]').should('not.exist'); From 5a114ae057b1ed7220f6cb1e0d63a9f4821eb1e0 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 5 Aug 2025 14:48:44 +0530 Subject: [PATCH 5/8] Unify phone input for the US and International format. --- assets/js/mailchimp.js | 15 ++++++ includes/blocks/mailchimp-form-field/edit.js | 45 +++++----------- includes/class-mailchimp-form-submission.php | 56 ++++++-------------- includes/mailchimp-deprecated-functions.php | 3 ++ mailchimp.php | 3 +- mailchimp_widget.php | 13 ++--- 6 files changed, 54 insertions(+), 81 deletions(-) diff --git a/assets/js/mailchimp.js b/assets/js/mailchimp.js index a27900ea..753c8ab9 100644 --- a/assets/js/mailchimp.js +++ b/assets/js/mailchimp.js @@ -93,4 +93,19 @@ }); }); } + + // Phone validation custom error message. + if ($('.mailchimp-sf-phone').length > 0) { + $('.mailchimp-sf-phone').each(function () { + $(this) + .on('input', function () { + this.setCustomValidity(''); + }) + .on('invalid', function () { + if (!this.validity.valid) { + this.setCustomValidity(window.mailchimpSF.phone_validation_error); + } + }); + }); + } })(window.jQuery); diff --git a/includes/blocks/mailchimp-form-field/edit.js b/includes/blocks/mailchimp-form-field/edit.js index 115eb274..162bc426 100644 --- a/includes/blocks/mailchimp-form-field/edit.js +++ b/includes/blocks/mailchimp-form-field/edit.js @@ -232,37 +232,20 @@ export const MailchimpFormField = (props) => { ); case 'phone': - if (field?.options?.phone_format === 'US') { - return ( - <> - - - - - ); - } - return ; + // eslint-disable-next-line no-case-declarations + const isUSPhone = field?.options?.phone_format === 'US'; + // eslint-disable-next-line no-case-declarations + const placeholder = isUSPhone ? '(###) ### - ####' : ''; + return ( + + ); case 'email': case 'url': diff --git a/includes/class-mailchimp-form-submission.php b/includes/class-mailchimp-form-submission.php index a3eb3934..15d0aaa6 100644 --- a/includes/class-mailchimp-form-submission.php +++ b/includes/class-mailchimp-form-submission.php @@ -176,42 +176,29 @@ public function handle_form_submission() { /** * Validate phone * - * @param array $opt_val Option value. - * @param array $data Data. + * @param string $opt_val Option value. + * @param array $data Data. * @return string|WP_Error Option value or error. */ public function validate_phone( $opt_val, $data ) { - // This filters out all 'falsy' elements - $opt_val = array_filter( $opt_val ); - // If they weren't all empty if ( empty( $opt_val ) ) { return ''; } - // Trim the phone number - $opt_val = array_map( - function ( $ele ) { - return preg_replace( '/\s+/', '', trim( $ele ) ); - }, - $opt_val - ); - - $opt_val = implode( '-', $opt_val ); - if ( strlen( $opt_val ) !== 12 ) { - // translators: %s: field name - $message = sprintf( esc_html__( '%s should be 10 digits long.', 'mailchimp' ), esc_html( $data['name'] ) ); - $error = new WP_Error( 'mc_phone_validation', $message ); - return $error; + // Backwards compatibility for old phone format. + if ( is_array( $opt_val ) ) { + $opt_val = implode( '-', $opt_val ); } - if ( ! preg_match( '/^[0-9]{3}-[0-9]{3}-[0-9]{4}$/', $opt_val ) ) { - /* translators: %s: field name */ - $message = sprintf( esc_html__( '%s must consist of only numbers', 'mailchimp' ), esc_html( $data['name'] ) ); - $error = new WP_Error( 'mc_phone_validation', $message ); - return $error; - } + $opt_val = trim( $opt_val ); - return $opt_val; + // Validate phone number. + if ( preg_match( '/^\+?[\d\s\-\(\)\.]*$/', $opt_val ) ) { + return $opt_val; + } else { + $message = sprintf( esc_html__( 'Please enter a valid %s.', 'mailchimp' ), esc_html( $data['name'] ) ); + return new WP_Error( 'mc_phone_validation', $message ); + } } /** @@ -265,21 +252,12 @@ public function prepare_merge_fields_body( $merge_fields ) { switch ( $merge_field['type'] ) { /** - * US Phone validation - * - * - Merge field is phone - * - Phone format is set in Mailchimp account - * - Phone format is US in Mailchimp account + * US/International Phone validation */ case 'phone': - if ( - isset( $merge_field['options']['phone_format'] ) - && 'US' === $merge_field['options']['phone_format'] - ) { - $opt_val = $this->validate_phone( $opt_val, $merge_field ); - if ( is_wp_error( $opt_val ) ) { - return $opt_val; - } + $opt_val = $this->validate_phone( $opt_val, $merge_field ); + if ( is_wp_error( $opt_val ) ) { + return $opt_val; } break; diff --git a/includes/mailchimp-deprecated-functions.php b/includes/mailchimp-deprecated-functions.php index b30e4a90..f3885cde 100644 --- a/includes/mailchimp-deprecated-functions.php +++ b/includes/mailchimp-deprecated-functions.php @@ -127,6 +127,9 @@ function mailchimp_sf_merge_remove_empty( $merge ) { function mailchimp_sf_merge_validate_phone( $opt_val, $data ): string|WP_Error { _deprecated_function( __FUNCTION__, 'x.x.x', 'Mailchimp_Form_Submission::validate_phone()' ); + if ( is_array( $opt_val ) ) { + $opt_val = implode( '-', $opt_val ); + } $form_submission = new Mailchimp_Form_Submission(); return $form_submission->validate_phone( $opt_val, $data ); } diff --git a/mailchimp.php b/mailchimp.php index a738e676..1082e59b 100644 --- a/mailchimp.php +++ b/mailchimp.php @@ -164,7 +164,8 @@ function mailchimp_sf_load_resources() { 'mailchimp_sf_main_js', 'mailchimpSF', array( - 'ajax_url' => trailingslashit( home_url() ), + 'ajax_url' => trailingslashit( home_url() ), + 'phone_validation_error' => esc_html__( 'Please enter a valid phone number.', 'mailchimp' ), ) ); diff --git a/mailchimp_widget.php b/mailchimp_widget.php index 1154082a..aa8b7673 100644 --- a/mailchimp_widget.php +++ b/mailchimp_widget.php @@ -420,17 +420,10 @@ function mailchimp_form_field( $data, $num_fields, $should_display = null, $labe '; break; case 'phone': - if ( isset( $data['options']['phone_format'] ) && 'US' === $data['options']['phone_format'] ) { - $html .= ' - - - - '; - } else { - $html .= ' - + $is_us_phone = isset( $data['options']['phone_format'] ) && 'US' === $data['options']['phone_format']; + $html .= ' + '; - } break; case 'email': case 'url': From 3ab6997e267c663ba89f957599272124adfc0554 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 5 Aug 2025 15:44:51 +0530 Subject: [PATCH 6/8] E2E: Update E2E tests. --- mailchimp_widget.php | 2 +- tests/cypress/e2e/validation/us-phone.test.js | 46 ++----------------- .../validate-required-fields.test.js | 27 ++--------- 3 files changed, 9 insertions(+), 66 deletions(-) diff --git a/mailchimp_widget.php b/mailchimp_widget.php index aa8b7673..ceca7a96 100644 --- a/mailchimp_widget.php +++ b/mailchimp_widget.php @@ -422,7 +422,7 @@ function mailchimp_form_field( $data, $num_fields, $should_display = null, $labe case 'phone': $is_us_phone = isset( $data['options']['phone_format'] ) && 'US' === $data['options']['phone_format']; $html .= ' - + '; break; case 'email': diff --git a/tests/cypress/e2e/validation/us-phone.test.js b/tests/cypress/e2e/validation/us-phone.test.js index 68ab4c53..e1220ac8 100644 --- a/tests/cypress/e2e/validation/us-phone.test.js +++ b/tests/cypress/e2e/validation/us-phone.test.js @@ -4,25 +4,11 @@ import { generateRandomEmail } from '../../support/functions/utility'; /** * Test for Phone Number Validation */ -describe('US Multi-Input Phone Number Validation', () => { +describe('Phone Number Validation', () => { let blockPostPostURL; - const validPhones = [ - { area: '123', detail1: '456', detail2: '7890' }, - { area: '987', detail1: '654', detail2: '3210' }, - ]; - const invalidPhones = [ - { area: '123', detail1: '456', detail2: '78a0' }, - { area: '123', detail1: '45!', detail2: '7890' }, - ]; - const tooShortPhones = [ - { area: '12', detail1: '456', detail2: '789' }, - { area: '1', detail1: '45', detail2: '7890' }, - ]; - const tooLongPhones = [ - { area: '1234', detail1: '567', detail2: '890' }, - { area: '123', detail1: '4567', detail2: '8901' }, - ]; + const validPhones = ['1234567890', '+1 (234) 567-890']; + const invalidPhones = ['12345678a0', '12345!7890']; before(() => { cy.login(); @@ -54,9 +40,7 @@ describe('US Multi-Input Phone Number Validation', () => { }); function fillPhoneInputs(phone) { - cy.get('#mc_mv_PHONE-area').clear().type(phone.area); - cy.get('#mc_mv_PHONE-detail1').clear().type(phone.detail1); - cy.get('#mc_mv_PHONE-detail2').clear().type(phone.detail2); + cy.get('#mc_mv_PHONE').clear().type(phone); } it('Valid phone numbers', () => { @@ -81,27 +65,7 @@ describe('US Multi-Input Phone Number Validation', () => { cy.get('#mc_mv_EMAIL').type(email); fillPhoneInputs(phone); cy.submitFormAndVerifyError(); - cy.get('.mc_error_msg').contains('must consist of only numbers'); - }); - }); - - it('Phone length validation', () => { - cy.visit(blockPostPostURL); - - tooShortPhones.forEach((phone) => { - const email = generateRandomEmail('shortphone'); - cy.get('#mc_mv_EMAIL').type(email); - fillPhoneInputs(phone); - cy.submitFormAndVerifyError(); - cy.get('.mc_error_msg').contains('should be 10 digits long'); - }); - - tooLongPhones.forEach((phone) => { - const email = generateRandomEmail('longphone'); - cy.get('#mc_mv_EMAIL').type(email); - fillPhoneInputs(phone); - cy.submitFormAndVerifyError(); - cy.get('.mc_error_msg').contains('should be 10 digits long'); + cy.get('.mc_error_msg').contains('Please enter a valid Phone Number.'); }); }); }); diff --git a/tests/cypress/e2e/validation/validate-required-fields.test.js b/tests/cypress/e2e/validation/validate-required-fields.test.js index eb45aa4f..f9e79c14 100644 --- a/tests/cypress/e2e/validation/validate-required-fields.test.js +++ b/tests/cypress/e2e/validation/validate-required-fields.test.js @@ -98,14 +98,7 @@ describe('Validate required fields', () => { cy.get('#mc_mv_EMAIL').clear().type(email); // Email is always required requiredFields.forEach((field) => { - if (field.selector === '#mc_mv_PHONE') { - const phone = field.input.split('-'); - cy.get('#mc_mv_PHONE-area').clear().type(phone[0]); - cy.get('#mc_mv_PHONE-detail1').clear().type(phone[1]); - cy.get('#mc_mv_PHONE-detail2').clear().type(phone[2]); - } else { - cy.get(field.selector).clear().type(field.input); - } + cy.get(field.selector).clear().type(field.input); cy.get('body').click(0, 0); // Click outside the field to clear the datepicker modal }); @@ -131,14 +124,7 @@ describe('Validate required fields', () => { // Test validation for each required field requiredFields.forEach((field) => { - // Submit the form without input to trigger validation - if (field.selector === '#mc_mv_PHONE') { - cy.get('#mc_mv_PHONE-area').clear(); - cy.get('#mc_mv_PHONE-detail1').clear(); - cy.get('#mc_mv_PHONE-detail2').clear(); - } else { - cy.get(field.selector).clear(); // Ensure field is empty - } + cy.get(field.selector).clear(); // Ensure field is empty cy.get('body').click(0, 0); // Click outside the field to clear the datepicker modal cy.get('#mc_signup_submit').click(); @@ -147,14 +133,7 @@ describe('Validate required fields', () => { cy.get('.mc_error_msg').should('include.text', field.errorMessage); // Fill in the field - if (field.selector === '#mc_mv_PHONE') { - const phone = field.input.split('-'); - cy.get('#mc_mv_PHONE-area').clear().type(phone[0]); - cy.get('#mc_mv_PHONE-detail1').clear().type(phone[1]); - cy.get('#mc_mv_PHONE-detail2').clear().type(phone[2]); - } else { - cy.get(field.selector).type(field.input); - } + cy.get(field.selector).type(field.input); cy.get('body').click(0, 0); // Click outside the field to clear the datepicker modal }); }); From b0bfcf2c1b94ad8ed03a6d8bf28b41695f66c480 Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 5 Aug 2025 15:54:31 +0530 Subject: [PATCH 7/8] Fix Spacing issue. --- includes/class-mailchimp-form-submission.php | 1 + mailchimp_widget.php | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/includes/class-mailchimp-form-submission.php b/includes/class-mailchimp-form-submission.php index 15d0aaa6..2b98e561 100644 --- a/includes/class-mailchimp-form-submission.php +++ b/includes/class-mailchimp-form-submission.php @@ -196,6 +196,7 @@ public function validate_phone( $opt_val, $data ) { if ( preg_match( '/^\+?[\d\s\-\(\)\.]*$/', $opt_val ) ) { return $opt_val; } else { + /* translators: %s: field name */ $message = sprintf( esc_html__( 'Please enter a valid %s.', 'mailchimp' ), esc_html( $data['name'] ) ); return new WP_Error( 'mc_phone_validation', $message ); } diff --git a/mailchimp_widget.php b/mailchimp_widget.php index ceca7a96..049bd2fb 100644 --- a/mailchimp_widget.php +++ b/mailchimp_widget.php @@ -421,9 +421,8 @@ function mailchimp_form_field( $data, $num_fields, $should_display = null, $labe break; case 'phone': $is_us_phone = isset( $data['options']['phone_format'] ) && 'US' === $data['options']['phone_format']; - $html .= ' - - '; + $html .= ' + '; break; case 'email': case 'url': From 213c12de8cc5573b47af4ceb39922a33ee36f47d Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 5 Aug 2025 16:23:33 +0530 Subject: [PATCH 8/8] E2E: fix failing test. --- tests/cypress/e2e/validation/us-phone.test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/cypress/e2e/validation/us-phone.test.js b/tests/cypress/e2e/validation/us-phone.test.js index e1220ac8..36cf3b80 100644 --- a/tests/cypress/e2e/validation/us-phone.test.js +++ b/tests/cypress/e2e/validation/us-phone.test.js @@ -64,8 +64,10 @@ describe('Phone Number Validation', () => { const email = generateRandomEmail('invalidphone'); cy.get('#mc_mv_EMAIL').type(email); fillPhoneInputs(phone); - cy.submitFormAndVerifyError(); - cy.get('.mc_error_msg').contains('Please enter a valid Phone Number.'); + cy.get('#mc_signup_submit').click(); + cy.get('#mc_mv_PHONE:invalid') + .invoke('prop', 'validationMessage') + .should('equal', 'Please enter a valid phone number.'); }); }); });