From d66e6a69e0d47960f041e095cf585d894704544b Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 15:28:48 +0100 Subject: [PATCH 01/19] Add a class to deal with various email opt-in functions --- admin/opt-in/class-email-opt-in.php | 161 ++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 admin/opt-in/class-email-opt-in.php diff --git a/admin/opt-in/class-email-opt-in.php b/admin/opt-in/class-email-opt-in.php new file mode 100644 index 00000000..850b7980 --- /dev/null +++ b/admin/opt-in/class-email-opt-in.php @@ -0,0 +1,161 @@ + wp_create_nonce( 'ajax-nonce' ), + 'ajaxurl' => admin_url( 'admin-ajax.php' ), + 'showModal' => self::should_show_modal(), + ) + ); + + if ( self::should_show_modal() ) { + // user modal needs thickbox (and thus jquery). + add_thickbox(); + // Also needs to output the markup for use in the modal. + add_action( 'admin_footer', array( $this, 'render_modal' ) ); + } + } + + /** + * Renders the opt-in modal markup that thickbox plucks content from to display. + * + * @return void + */ + public function render_modal(): void { + ?> + + +
+
+ + + + + + + + +
+
+

+ +

+
+
+
+

+ +

+
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+
+ Date: Tue, 16 Apr 2024 15:29:25 +0100 Subject: [PATCH 02/19] Add ajax handler for user closing opt in modal --- admin/class-ajax.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/admin/class-ajax.php b/admin/class-ajax.php index ecae7831..2aa36569 100644 --- a/admin/class-ajax.php +++ b/admin/class-ajax.php @@ -7,6 +7,7 @@ namespace EDAC\Admin; +use EDAC\Admin\OptIn\Email_Opt_In; use EDAC\Inc\Summary_Generator; /** @@ -34,6 +35,7 @@ public function init_hooks() { add_action( 'wp_ajax_edac_dismiss_welcome_cta_ajax', array( $this, 'dismiss_welcome_cta' ) ); add_action( 'wp_ajax_edac_dismiss_dashboard_cta_ajax', array( $this, 'dismiss_dashboard_cta' ) ); add_action( 'wp_ajax_edac_email_opt_in_ajax', array( $this, 'email_opt_in' ) ); + add_action( 'wp_ajax_edac_email_opt_in_closed_modal_ajax', array( $this, 'handle_email_opt_in_closed_modal' ) ); } /** @@ -748,4 +750,16 @@ public function email_opt_in() { wp_send_json( 'success' ); } + + /** + * Handle AJAX request to indicate user has seen the email opt-in modal and closed it. + * + * This prevents the modal from showing again for the current user. + * + * @return void + */ + public function handle_email_opt_in_closed_modal() { + update_user_meta( get_current_user_id(), Email_Opt_In::EDAC_USER_OPTIN_SHOW_MODAL_KEY, true ); + wp_send_json( 'success' ); + } } From 603cf30c3fbf942aa3e16d59c8270105263ad296 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 15:30:42 +0100 Subject: [PATCH 03/19] Handle admin email opt-in scripts and styles through Email_Opt_In class --- admin/class-enqueue-admin.php | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/admin/class-enqueue-admin.php b/admin/class-enqueue-admin.php index f4c590bc..17c89956 100644 --- a/admin/class-enqueue-admin.php +++ b/admin/class-enqueue-admin.php @@ -7,6 +7,8 @@ namespace EDAC\Admin; +use EDAC\Admin\OptIn\Email_Opt_In; + /** * Class that initializes and handles enqueueing styles and scripts for the admin. */ @@ -135,22 +137,16 @@ public static function maybe_enqueue_admin_and_editor_app_scripts() { public static function maybe_enqueue_email_opt_in_script() { $page = isset( $_GET['page'] ) ? sanitize_text_field( $_GET['page'] ) : null; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- display only. - if ( 'accessibility_checker' === $page - && true !== (bool) get_user_meta( get_current_user_id(), 'edac_email_optin', true ) - ) { - - wp_enqueue_style( 'email-opt-in-form', plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/css/emailOptIn.css', false, EDAC_VERSION, 'all' ); - wp_enqueue_script( 'email-opt-in-form', plugin_dir_url( EDAC_PLUGIN_FILE ) . 'build/emailOptIn.bundle.js', false, EDAC_VERSION, true ); - - wp_localize_script( - 'email-opt-in-form', - 'edac_email_opt_in_form', - array( - 'nonce' => wp_create_nonce( 'ajax-nonce' ), - 'ajaxurl' => admin_url( 'admin-ajax.php' ), - ) - ); + if ( 'accessibility_checker' !== $page ) { + return; + } + $user_already_opted_in = (bool) get_user_meta( get_current_user_id(), Email_Opt_In::EDAC_USER_OPTIN_META_KEY, true ); + if ( $user_already_opted_in ) { + return; } + + $email_opt_in = new Email_Opt_In(); + $email_opt_in->enqueue_scripts(); } } From 4d133005b48aba3550e073f86e5e0e19f9212710 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 15:42:06 +0100 Subject: [PATCH 04/19] Handle the sidebar opt-in through Email_Opt_In class --- admin/class-welcome-page.php | 77 ++++-------------------------------- 1 file changed, 7 insertions(+), 70 deletions(-) diff --git a/admin/class-welcome-page.php b/admin/class-welcome-page.php index 770d074e..438da02f 100644 --- a/admin/class-welcome-page.php +++ b/admin/class-welcome-page.php @@ -9,6 +9,8 @@ namespace EDAC\Admin; +use EDAC\Admin\OptIn\Email_Opt_In; + /** * Class that handles welcome page */ @@ -322,80 +324,15 @@ public static function render_summary() { * @return void */ public static function maybe_render_email_opt_in() { - - if ( true === (bool) get_user_meta( get_current_user_id(), 'edac_email_optin', true ) ) { - return; - } - - $current_user = wp_get_current_user(); - $email = $current_user->user_email; - $first_name = $current_user->first_name; - if ( empty( $email ) ) { - $email = ''; + if ( Email_Opt_In::user_already_subscribed() ) { + return; } - if ( empty( $first_name ) ) { - $first_name = ''; + if ( Email_Opt_In::should_show_modal() ) { + return; } - ?> - -
-
- - - - - - - - -
-
-

- -

-
-
-
-

- -

-
-
-
- -
- -
-
-
- -
- -
-
-
- -
-
-
-
- -
-
- Date: Tue, 16 Apr 2024 15:42:23 +0100 Subject: [PATCH 05/19] Fix typo in comment --- admin/class-welcome-page.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/admin/class-welcome-page.php b/admin/class-welcome-page.php index 438da02f..58414f5d 100644 --- a/admin/class-welcome-page.php +++ b/admin/class-welcome-page.php @@ -317,9 +317,8 @@ public static function render_summary() { Date: Tue, 16 Apr 2024 15:44:07 +0100 Subject: [PATCH 06/19] Use simplified FQD for Welcome_Page class --- partials/welcome-page.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/partials/welcome-page.php b/partials/welcome-page.php index 9bf2ced5..5c148d47 100644 --- a/partials/welcome-page.php +++ b/partials/welcome-page.php @@ -5,6 +5,8 @@ * @package Accessibility_Checker */ +use EDAC\Admin\Welcome_Page; + ?>
@@ -40,7 +42,7 @@
- +
@@ -175,7 +177,7 @@

- +
@@ -206,7 +208,7 @@ - + - + From 3fbb4b335fec124d9eb4f0af28b6de008768f155 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 15:45:03 +0100 Subject: [PATCH 07/19] Add JS code to handle display of the opt-in modal where it's used --- src/emailOptIn/modal.js | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 src/emailOptIn/modal.js diff --git a/src/emailOptIn/modal.js b/src/emailOptIn/modal.js new file mode 100644 index 00000000..7967d010 --- /dev/null +++ b/src/emailOptIn/modal.js @@ -0,0 +1,43 @@ +/** + * Handle the opt-in modal for first time visitors to welcome page. + * + * This relies on the Thickbox library that is included in WordPress core which relies on jQuery. + */ + +/* global tb_show */ + +import { createFocusTrap } from 'focus-trap'; + +// Ensure the global variable is defined. +window.edac_email_opt_in_form = window.edac_email_opt_in_form || {}; + +export const initOptInModal = () => { + window.onload = function() { + tb_show( 'Accessibility Checker', '#TB_inline?width=600&inlineId=edac-opt-in-modal', null ); + + // a small delay is needed to ensure the modal is fully loaded before creating a focus trap. + setTimeout( + function() { + document.getElementById( 'TB_window' ).querySelector( 'input' ).focus(); + + const focusTrap = createFocusTrap( '#TB_window' ); + focusTrap.activate(); + + jQuery( document ).off( 'tb_unload' ).on( + 'tb_unload', + function() { + onModalClose( focusTrap ); + } + ); + }, + 200 + ); + }; +}; + +const onModalClose = ( focusTrap ) => { + focusTrap.deactivate(); + + fetch( window.edac_email_opt_in_form.ajaxurl + '?action=edac_email_opt_in_closed_modal_ajax' ) + .then( ( r ) => r.json() ); +}; From 69b709a4ceb48ab3d6fcb6f320c413d9acebf858 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 15:45:46 +0100 Subject: [PATCH 08/19] Invoke modal from emailOptIn bundle where it's in use --- src/emailOptIn/index.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/emailOptIn/index.js b/src/emailOptIn/index.js index ce605184..23374c7f 100644 --- a/src/emailOptIn/index.js +++ b/src/emailOptIn/index.js @@ -1,5 +1,7 @@ /* eslint-disable */ +import {initOptInModal} from "./modal"; + const edac_on_submit_ok = function () { const data = { action: "edac_email_opt_in_ajax", nonce: edac_email_opt_in_form.nonce }; const queryString = Object.keys(data) @@ -498,4 +500,8 @@ window._load_script = function (url, callback, isSubmit) { return false; }; addEvent(form_to_submit, 'submit', form_submit); + // Added in plugin version 1.11.0 and triggers through `window.onload` event. + if ( window.edac_email_opt_in_form.showModal ) { + initOptInModal(); + } })(); From 28b6199b3c23bf99686cc315dc9ac4358832d09c Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 15:46:16 +0100 Subject: [PATCH 09/19] Delete whole block of commented out style rules --- src/emailOptIn/sass/email-opt-in.scss | 121 ++------------------------ 1 file changed, 7 insertions(+), 114 deletions(-) diff --git a/src/emailOptIn/sass/email-opt-in.scss b/src/emailOptIn/sass/email-opt-in.scss index c459e9e2..417ee123 100644 --- a/src/emailOptIn/sass/email-opt-in.scss +++ b/src/emailOptIn/sass/email-opt-in.scss @@ -1,116 +1,9 @@ -/* +#_form_1_ { + padding: 0; + margin: 0; + input { + width: 100%; + } +} -// This is the original css provided by ActiveCampaign. -@import url(https://fonts.bunny.net/css?family=ibm-plex-sans:400,600); - #_form_1_ { font-size:14px; line-height:1.6; font-family:arial, helvetica, sans-serif; margin:0; } - #_form_1_ * { outline:0; } - ._form_hide { display:none; visibility:hidden; } - ._form_show { display:block; visibility:visible; } - #_form_1_._form-top { top:0; } - #_form_1_._form-bottom { bottom:0; } - #_form_1_._form-left { left:0; } - #_form_1_._form-right { right:0; } - #_form_1_ input[type="text"],#_form_1_ input[type="tel"],#_form_1_ input[type="date"],#_form_1_ textarea { padding:6px; height:auto; border:#979797 1px solid; border-radius:4px; color:#000 !important; font-size:14px; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; } - #_form_1_ textarea { resize:none; } - #_form_1_ ._submit { -webkit-appearance:none; cursor:pointer; font-family:arial, sans-serif; font-size:14px; text-align:center; background:#015798 !important; border:0 !important; -moz-border-radius:4px !important; -webkit-border-radius:4px !important; border-radius:4px !important; color:#fff !important; padding:10px !important; } - #_form_1_ ._submit:disabled { cursor:not-allowed; opacity:0.4; } - #_form_1_ ._submit.processing { position:relative; } - #_form_1_ ._submit.processing::before { content:''; width:1em; height:1em; position:absolute; z-index:1; top:50%; left:50%; border:double 3px transparent; border-radius:50%; background-image:linear-gradient(#015798, #015798), conic-gradient(#015798, #fff); background-origin:border-box; background-clip:content-box, border-box; animation:1200ms ease 0s infinite normal none running _spin; } - #_form_1_ ._submit.processing::after { content:''; position:absolute; top:0; bottom:0; left:0; right:0; background:#015798 !important; border:0 !important; -moz-border-radius:4px !important; -webkit-border-radius:4px !important; border-radius:4px !important; color:#fff !important; padding:10px !important; } - @keyframes _spin { 0% { transform:translate(-50%, -50%) rotate(90deg); } - 100% { transform:translate(-50%, -50%) rotate(450deg); } - } - #_form_1_ ._close-icon { cursor:pointer; background-image:url('https://d226aj4ao1t61q.cloudfront.net/esfkyjh1u_forms-close-dark.png'); background-repeat:no-repeat; background-size:14.2px 14.2px; position:absolute; display:block; top:11px; right:9px; overflow:hidden; width:16.2px; height:16.2px; } - #_form_1_ ._close-icon:before { position:relative; } - //#_form_1_ ._form-body { margin-bottom:30px; } - #_form_1_ ._form-image-left { width:150px; float:left; } - #_form_1_ ._form-content-right { margin-left:164px; } - #_form_1_ ._form-branding { color:#fff; font-size:10px; clear:both; text-align:left; margin-top:30px; font-weight:100; } - #_form_1_ ._form-branding ._logo { display:block; width:130px; height:14px; margin-top:6px; background-image:url('https://d226aj4ao1t61q.cloudfront.net/hh9ujqgv5_aclogo_li.png'); background-size:130px auto; background-repeat:no-repeat; } - #_form_1_ .form-sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0, 0, 0, 0); border:0; } - #_form_1_ ._form-label,#_form_1_ ._form_element ._form-label { font-weight:bold; margin-bottom:5px; display:block; } - #_form_1_._dark ._form-branding { color:#333; } - #_form_1_._dark ._form-branding ._logo { background-image:url('https://d226aj4ao1t61q.cloudfront.net/jftq2c8s_aclogo_dk.png'); } - #_form_1_ ._form_element { position:relative; margin-bottom:10px; font-size:0; max-width:100%; } - #_form_1_ ._form_element * { font-size:14px; } - #_form_1_ ._form_element._clear { clear:both; width:100%; float:none; } - #_form_1_ ._form_element._clear:after { clear:left; } - #_form_1_ ._form_element input[type="text"],#_form_1_ ._form_element input[type="date"],#_form_1_ ._form_element select,#_form_1_ ._form_element textarea:not(.g-recaptcha-response) { display:block; width:100%; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; font-family:inherit; } - #_form_1_ ._field-wrapper { position:relative; } - #_form_1_ ._inline-style { float:left; } - #_form_1_ ._inline-style input[type="text"] { width:150px; } - #_form_1_ ._inline-style:not(._clear) + ._inline-style:not(._clear) { margin-left:20px; } - #_form_1_ ._form_element img._form-image { max-width:100%; } - #_form_1_ ._form_element ._form-fieldset { border:0; padding:0.01em 0 0 0; margin:0; min-width:0; } - #_form_1_ ._clear-element { clear:left; } - #_form_1_ ._full_width { width:100%; } - #_form_1_ ._form_full_field { display:block; width:100%; margin-bottom:10px; } - #_form_1_ input[type="text"]._has_error,#_form_1_ textarea._has_error { border:#f37c7b 1px solid; } - #_form_1_ input[type="checkbox"]._has_error { outline:#f37c7b 1px solid; } - #_form_1_ ._error { display:block; position:absolute; font-size:14px; z-index:10000001; } - #_form_1_ ._error._above { padding-bottom:4px; bottom:39px; right:0; } - #_form_1_ ._error._below { padding-top:8px; top:100%; right:0; } - #_form_1_ ._error._above ._error-arrow { bottom:-4px; right:15px; border-left:8px solid transparent; border-right:8px solid transparent; border-top:8px solid #fdd; } - #_form_1_ ._error._below ._error-arrow { top:0; right:15px; border-left:8px solid transparent; border-right:8px solid transparent; border-bottom:8px solid #fdd; } - #_form_1_ ._error-inner { padding:12px 12px 12px 36px; background-color:#fdd; background-image:url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M16 8C16 12.4183 12.4183 16 8 16C3.58172 16 0 12.4183 0 8C0 3.58172 3.58172 0 8 0C12.4183 0 16 3.58172 16 8ZM9 3V9H7V3H9ZM9 13V11H7V13H9Z' fill='%23CA0000'/%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:12px center; font-size:14px; font-family:arial, sans-serif; font-weight:600; line-height:16px; color:#000; text-align:center; text-decoration:none; -webkit-border-radius:4px; -moz-border-radius:4px; border-radius:4px; box-shadow:0px 1px 4px rgba(31, 33, 41, 0.298295); } - #_form_1_ ._error-inner._form_error { margin-bottom:5px; text-align:left; } - #_form_1_ ._button-wrapper ._error-inner._form_error { position:static; } - #_form_1_ ._error-inner._no_arrow { margin-bottom:10px; } - #_form_1_ ._error-arrow { position:absolute; width:0; height:0; } - #_form_1_ ._error-html { margin-bottom:10px; } - .pika-single { z-index:10000001 !important; } - #_form_1_ input[type="text"].datetime_date { width:69%; display:inline; } - #_form_1_ select.datetime_time { width:29%; display:inline; height:32px; } - #_form_1_ input[type="date"].datetime_date { width:69%; display:inline-flex; } - #_form_1_ input[type="time"].datetime_time { width:29%; display:inline-flex; } - @media all and (min-width:320px) and (max-width:667px) { ::-webkit-scrollbar { display:none; } - #_form_1_ { margin:0; width:100%; min-width:100%; max-width:100%; box-sizing:border-box; } - #_form_1_ * { -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; font-size:1em; } - #_form_1_ ._form-content { margin:0; width:100%; } - #_form_1_ ._form-inner { display:block; min-width:100%; } - #_form_1_ ._form-title,#_form_1_ ._inline-style { margin-top:0; margin-right:0; margin-left:0; } - #_form_1_ ._form-title { font-size:1.2em; } - #_form_1_ ._form_element { margin:0 0 20px; padding:0; width:100%; } - #_form_1_ ._form-element,#_form_1_ ._inline-style,#_form_1_ input[type="text"],#_form_1_ label,#_form_1_ p,#_form_1_ textarea:not(.g-recaptcha-response) { float:none; display:block; width:100%; } - #_form_1_ ._row._checkbox-radio label { display:inline; } - #_form_1_ ._row,#_form_1_ p,#_form_1_ label { margin-bottom:0.7em; width:100%; } - #_form_1_ ._row input[type="checkbox"],#_form_1_ ._row input[type="radio"] { margin:0 !important; vertical-align:middle !important; } - #_form_1_ ._row input[type="checkbox"] + span label { display:inline; } - #_form_1_ ._row span label { margin:0 !important; width:initial !important; vertical-align:middle !important; } - #_form_1_ ._form-image { max-width:100%; height:auto !important; } - #_form_1_ input[type="text"] { padding-left:10px; padding-right:10px; font-size:16px; line-height:1.3em; -webkit-appearance:none; } - #_form_1_ input[type="radio"],#_form_1_ input[type="checkbox"] { display:inline-block; width:1.3em; height:1.3em; font-size:1em; margin:0 0.3em 0 0; vertical-align:baseline; } - #_form_1_ button[type="submit"] { padding:20px; font-size:1.5em; } - #_form_1_ ._inline-style { margin:20px 0 0 !important; } - } - #_form_1_ { position:relative; text-align:left; margin:25px auto 0; padding-top:20px; padding-right:20px; padding-bottom:20px; padding-left:20px; -webkit-box-sizing:border-box; -moz-box-sizing:border-box; box-sizing:border-box; *zoom:1; background:#fff !important; border:0px solid #b0b0b0 !important; max-width:500px; -moz-border-radius:0px !important; -webkit-border-radius:0px !important; border-radius:0px !important; color:#000 !important; } - #_form_1_._inline-form,#_form_1_._inline-form ._form-content,#_form_1_._inline-form input,#_form_1_._inline-form ._submit,#_form_1_._inline-form ._form-label,#_form_1_._inline-form ._html-code :not(h1):not(h2):not(h3):not(h4):not(h5):not(h6) { font-family:"IBM Plex Sans", Helvetica, sans-serif; } - #_form_1_._inline-form ._form-title { font-size:22px; line-height:normal; font-weight:600; margin-bottom:0; } - #_form_1_:before,#_form_1_:after { content:" "; display:table; } - #_form_1_:after { clear:both; } - #_form_1_._inline-style { width:auto; display:inline-block; } - #_form_1_._inline-style input[type="text"],#_form_1_._inline-style input[type="date"] { padding:10px 12px; } - #_form_1_._inline-style button._inline-style { position:relative; top:27px; } - #_form_1_._inline-style p { margin:0; } - #_form_1_._inline-style ._button-wrapper { position:relative; margin:27px 12.5px 0 20px; } - #_form_1_ ._form-thank-you { position:relative; left:0; right:0; text-align:center; font-size:18px; } - @media all and (min-width:320px) and (max-width:667px) { #_form_1_._inline-form._inline-style ._inline-style._button-wrapper { margin-top:20px !important; margin-left:0 !important; } - } - #_form_1_ .iti.iti--allow-dropdown.iti--separate-dial-code { width:100%; } - #_form_1_ .iti input { width:100%; height:32px; border:#979797 1px solid; border-radius:4px; } - #_form_1_ .iti--separate-dial-code .iti__selected-flag { background-color:#fff; border-radius:4px; } - #_form_1_ .iti--separate-dial-code .iti__selected-flag:hover { background-color:rgba(0, 0, 0, 0.05); } - #_form_1_ .iti__country-list { border-radius:4px; margin-top:4px; min-width:460px; } - #_form_1_ .iti__country-list--dropup { margin-bottom:4px; } - #_form_1_ .phone-error-hidden { display:none; } - #_form_1_ .phone-error { color:#e40e49; } - #_form_1_ .phone-input-error { border:1px solid #e40e49 !important; } -*/ - - - #_form_1_ { padding: 0; margin: 0;} - #_form_1_ input { width: 100%; } - - - \ No newline at end of file From 326d46d48e18155eb9c3fa02c03c24fdd8376cad Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 16:22:25 +0100 Subject: [PATCH 10/19] Move ajax handlers for email opt-in to class Email_Opt_In --- admin/class-ajax.php | 89 +++++++++++------------------ admin/opt-in/class-email-opt-in.php | 34 +++++++++++ 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/admin/class-ajax.php b/admin/class-ajax.php index 2aa36569..c292294c 100644 --- a/admin/class-ajax.php +++ b/admin/class-ajax.php @@ -34,8 +34,7 @@ public function init_hooks() { add_action( 'wp_ajax_edac_update_simplified_summary', array( $this, 'simplified_summary' ) ); add_action( 'wp_ajax_edac_dismiss_welcome_cta_ajax', array( $this, 'dismiss_welcome_cta' ) ); add_action( 'wp_ajax_edac_dismiss_dashboard_cta_ajax', array( $this, 'dismiss_dashboard_cta' ) ); - add_action( 'wp_ajax_edac_email_opt_in_ajax', array( $this, 'email_opt_in' ) ); - add_action( 'wp_ajax_edac_email_opt_in_closed_modal_ajax', array( $this, 'handle_email_opt_in_closed_modal' ) ); + ( new Email_Opt_In() )->register_ajax_handlers(); } /** @@ -315,25 +314,25 @@ function ( $a, $b ) { $html .= '
'; - $html .= '
'; + $html .= '
'; - $html .= '

'; - $html .= '' . $rule['count'] . ' '; - $html .= esc_html( $rule['title'] ); + $html .= '

'; + $html .= '' . $rule['count'] . ' '; + $html .= esc_html( $rule['title'] ); if ( $count_ignored > 0 ) { $html .= '' . $count_ignored . ' Ignored Items'; } - $html .= '

'; - $html .= ''; - $html .= ( $expand_rule ) ? '' : ''; + $html .= ''; + $html .= ''; + $html .= ( $expand_rule ) ? '' : ''; - $html .= '
'; + $html .= '
'; if ( $results ) { $html .= '
'; - $html .= + $html .= '
Date: Tue, 16 Apr 2024 16:45:05 +0100 Subject: [PATCH 11/19] Allow timeout to be passed to url_exists helper --- includes/helper-functions.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/includes/helper-functions.php b/includes/helper-functions.php index 7f678df5..a306f3d8 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -902,11 +902,17 @@ function edac_url_add_scheme_if_not_existing( string $file, string $site_protoco * @since 1.10.1 * * @param string $url the url to check. + * @param int $timeout the timeout in seconds. Default is 5. * @return bool */ -function edac_url_exists( string $url ): bool { +function edac_url_exists( string $url, int $timeout = 5 ): bool { - $response = wp_remote_head( $url ); + $response = wp_remote_head( + $url, + array( + 'timeout' => $timeout, + ) + ); if ( is_wp_error( $response ) || From 9fbe4bcc498aaa64ce3b94eafff1c7c0e3d682ce Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 16:45:40 +0100 Subject: [PATCH 12/19] Increase timeout for test_url_exists to avoid flaky test --- tests/phpunit/helper-functions/UrlExistsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/phpunit/helper-functions/UrlExistsTest.php b/tests/phpunit/helper-functions/UrlExistsTest.php index e2bda1a1..a6e9c9ad 100644 --- a/tests/phpunit/helper-functions/UrlExistsTest.php +++ b/tests/phpunit/helper-functions/UrlExistsTest.php @@ -17,7 +17,7 @@ class UrlExistsTest extends WP_UnitTestCase { */ public function test_url_exists() { $url = 'https://httpbin.org/status/200'; - $this->assertTrue( edac_url_exists( $url ) ); + $this->assertTrue( edac_url_exists( $url, 20 ) ); } /** From bb565f714c1220ac45538daea50dd2e95cbb6f54 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 18:33:33 +0100 Subject: [PATCH 13/19] Add tests for Email_Opt_In class --- admin/opt-in/class-email-opt-in.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/admin/opt-in/class-email-opt-in.php b/admin/opt-in/class-email-opt-in.php index d20b3a65..afbc8bea 100644 --- a/admin/opt-in/class-email-opt-in.php +++ b/admin/opt-in/class-email-opt-in.php @@ -15,7 +15,7 @@ class Email_Opt_In { const EDAC_USER_OPTIN_META_KEY = 'edac_email_optin'; - const EDAC_USER_OPTIN_SHOW_MODAL_KEY = 'edac_email_optin_seen_modal'; + const EDAC_USER_OPTIN_SEEN_MODAL_KEY = 'edac_email_optin_seen_modal'; /** * Checks if the current user already opted in. @@ -38,7 +38,7 @@ public static function user_already_subscribed(): bool { public static function should_show_modal(): bool { return ! (bool) get_user_meta( get_current_user_id(), - self::EDAC_USER_OPTIN_SHOW_MODAL_KEY, + self::EDAC_USER_OPTIN_SEEN_MODAL_KEY, true ); } @@ -189,7 +189,7 @@ public function handle_email_opt_in() { * @return void */ public function handle_email_opt_in_closed_modal() { - update_user_meta( get_current_user_id(), Email_Opt_In::EDAC_USER_OPTIN_SHOW_MODAL_KEY, true ); + update_user_meta( get_current_user_id(), Email_Opt_In::EDAC_USER_OPTIN_SEEN_MODAL_KEY, true ); wp_send_json( 'success' ); } } From 3752ef97b14bda637b146c0ffc910ac8e9a50f38 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 19:05:49 +0100 Subject: [PATCH 14/19] Add tests for Email_Opt_In class --- tests/phpunit/Admin/OptIn/EmailOptInTest.php | 201 +++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 tests/phpunit/Admin/OptIn/EmailOptInTest.php diff --git a/tests/phpunit/Admin/OptIn/EmailOptInTest.php b/tests/phpunit/Admin/OptIn/EmailOptInTest.php new file mode 100644 index 00000000..83416334 --- /dev/null +++ b/tests/phpunit/Admin/OptIn/EmailOptInTest.php @@ -0,0 +1,201 @@ +current_user_id = $this->factory()->user->create( + array( + 'role' => 'administrator', + 'user_email' => self::TEST_USER_EMAIL, + 'first_name' => self::TEST_USER_FIRST_NAME, + ) + ); + wp_set_current_user( $this->current_user_id ); + } + + /** + * Delete the user to clean up after tests. + */ + protected function tearDown(): void { + wp_delete_user( $this->current_user_id ); + } + + /** + * Test that the user_already_subscribed method returns false when the user has not subscribed. + */ + public function test_user_already_subscribed_returns_false_when_user_has_not_subscribed() { + $this->assertFalse( Email_Opt_In::user_already_subscribed() ); + } + + /** + * Test that the user_already_subscribed method returns true when the user has subscribed. + */ + public function test_user_already_subscribed_returns_true_when_user_is_subscribed() { + update_user_meta( $this->current_user_id, Email_Opt_In::EDAC_USER_OPTIN_META_KEY, true ); + $this->assertTrue( Email_Opt_In::user_already_subscribed() ); + } + + /** + * Test that the should_show_modal method returns true when the user has not seen the modal. + */ + public function test_should_show_modal_returns_true_when_user_has_not_seen_it_yet() { + $this->assertTrue( Email_Opt_In::should_show_modal() ); + } + + /** + * Test that the should_show_modal method returns false when the user has seen the modal. + */ + public function test_should_show_modal_returns_false_when_user_has_seen_it() { + update_user_meta( $this->current_user_id, Email_Opt_In::EDAC_USER_OPTIN_SEEN_MODAL_KEY, true ); + $this->assertFalse( Email_Opt_In::should_show_modal() ); + } + + /** + * Test that the enqueue_scripts method registers the scripts and styles needed for the opt-in modal. + */ + public function test_enqueue_scripts_registers_thickbox_when_user_has_not_seen_modal() { + $email_opt_in = new Email_Opt_In(); + $email_opt_in->enqueue_scripts(); + $this->assertTrue( wp_script_is( 'email-opt-in-form', 'enqueued' ) ); + $this->assertTrue( wp_style_is( 'email-opt-in-form', 'enqueued' ) ); + // check that admin footer has been hooked and thickbox is enqueued. + $this->assertEquals( 10, has_action( 'admin_footer', array( $email_opt_in, 'render_modal' ) ) ); + $this->assertTrue( wp_script_is( 'thickbox', 'enqueued' ) ); + } + + /** + * Test that the enqueue_scripts method does not add thickbox when the user has seen the modal. + */ + public function test_enqueue_scripts_does_not_add_thickbox_when_user_has_seen_modal() { + update_user_meta( $this->current_user_id, Email_Opt_In::EDAC_USER_OPTIN_SEEN_MODAL_KEY, true ); + $email_opt_in = new Email_Opt_In(); + $email_opt_in->enqueue_scripts(); + $this->assertTrue( wp_script_is( 'email-opt-in-form', 'enqueued' ) ); + $this->assertTrue( wp_style_is( 'email-opt-in-form', 'enqueued' ) ); + + // check that admin footer has NOT been hooked. + $this->assertFalse( has_action( 'admin_footer', array( $email_opt_in, 'render_modal' ) ) ); + } + + /** + * Test that the modal markup renders. + */ + public function test_form_renders_modal() { + $email_opt_in = new Email_Opt_In(); + $email_opt_in->render_modal(); + $this->expectOutputRegex( '/
render_form(); + $this->expectOutputRegex( '/.*?value="' . self::TEST_USER_EMAIL . '".*?/' ); + $this->expectOutputRegex( '/.*?value="' . self::TEST_USER_FIRST_NAME . '".*?/' ); + } + + /** + * Test that the ajax handlers are registered. + */ + public function test_ajax_handlers_registered() { + $email_opt_in = new Email_Opt_In(); + $email_opt_in->register_ajax_handlers(); + $this->assertEquals( 10, has_action( 'wp_ajax_edac_email_opt_in_ajax', array( $email_opt_in, 'handle_email_opt_in' ) ) ); + $this->assertEquals( 10, has_action( 'wp_ajax_edac_email_opt_in_closed_modal_ajax', array( $email_opt_in, 'handle_email_opt_in_closed_modal' ) ) ); + } + + /** + * Test that the email opt-in ajax handler creates the user meta. + */ + public function test_handle_email_opt_in() { + // mock the action in the $_POST global. + $_POST['action'] = 'edac_email_opt_in_ajax'; + $email_opt_in = new Email_Opt_In(); + + $this->filter_ajax_die_handler_to_exception_instead_of_actual_die(); + try { + ob_start(); + $email_opt_in->handle_email_opt_in(); + } catch ( Exception $e ) { + // We expected this, just clear the buffer.. + ob_end_clean(); + } + + $this->assertTrue( + (bool) get_user_meta( + get_current_user_id(), + Email_Opt_In::EDAC_USER_OPTIN_META_KEY, + true + ) + ); + } + + /** + * Test that the email opt-in closed modal ajax handler creates the user meta. + */ + public function test_handle_email_opt_in_closed_modal() { + // mock the action in the $_POST global. + $_POST['action'] = 'handle_email_opt_in_closed_modal_ajax'; + $email_opt_in = new Email_Opt_In(); + + $this->filter_ajax_die_handler_to_exception_instead_of_actual_die(); + try { + ob_start(); + $email_opt_in->handle_email_opt_in_closed_modal(); + } catch ( Exception $e ) { + // We expected this, just clear the buffer. + ob_end_clean(); + } + + $this->assertTrue( + (bool) get_user_meta( + get_current_user_id(), + Email_Opt_In::EDAC_USER_OPTIN_SEEN_MODAL_KEY, + true + ) + ); + } + + /** + * Filter the wp_die handler to throw an exception instead of actually dying, + * so that we can test the ajax handlers. + */ + private function filter_ajax_die_handler_to_exception_instead_of_actual_die() { + add_filter( 'wp_doing_ajax', '__return_true' ); + add_filter( + 'wp_die_ajax_handler', + function () { + return new Exception( 'wp_die called' ); + }, + 1, + 1 + ); + } +} From 4ab9eccbea3d7e4c6f8c2894618fa70652cd925f Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 19:17:07 +0100 Subject: [PATCH 15/19] Update ajax test die handler to use a callback pattern instead of callable --- tests/phpunit/Admin/OptIn/EmailOptInTest.php | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/tests/phpunit/Admin/OptIn/EmailOptInTest.php b/tests/phpunit/Admin/OptIn/EmailOptInTest.php index 83416334..8aa947ae 100644 --- a/tests/phpunit/Admin/OptIn/EmailOptInTest.php +++ b/tests/phpunit/Admin/OptIn/EmailOptInTest.php @@ -144,7 +144,7 @@ public function test_handle_email_opt_in() { ob_start(); $email_opt_in->handle_email_opt_in(); } catch ( Exception $e ) { - // We expected this, just clear the buffer.. + // We expected this, just clear the buffer. ob_end_clean(); } @@ -191,11 +191,21 @@ private function filter_ajax_die_handler_to_exception_instead_of_actual_die() { add_filter( 'wp_doing_ajax', '__return_true' ); add_filter( 'wp_die_ajax_handler', - function () { - return new Exception( 'wp_die called' ); - }, + array( $this, 'throw_exception_instead_of_dying' ), 1, 1 ); } + + /** + * Throw an exception instead of actually dying. + * + * @return Closure + */ + public function throw_exception_instead_of_dying() { + return function ( $message ) { + // phpcs:ignore WordPress.Security -- this is just a test. + throw new Exception( $message ); + }; + } } From 01d57cbac485b67f0c72eea42ea7975cee2b4ff0 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 19:29:10 +0100 Subject: [PATCH 16/19] Add a fail condition in setup to see why user creation fails --- tests/phpunit/Admin/OptIn/EmailOptInTest.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/phpunit/Admin/OptIn/EmailOptInTest.php b/tests/phpunit/Admin/OptIn/EmailOptInTest.php index 8aa947ae..6f06fc37 100644 --- a/tests/phpunit/Admin/OptIn/EmailOptInTest.php +++ b/tests/phpunit/Admin/OptIn/EmailOptInTest.php @@ -35,6 +35,11 @@ protected function setUp(): void { 'first_name' => self::TEST_USER_FIRST_NAME, ) ); + + if ( is_wp_error( $this->current_user_id ) ) { + $this->fail( $this->current_user_id->get_error_message() ); + } + wp_set_current_user( $this->current_user_id ); } From 1bf30333c5ba6ec220c1d3441ecb8e530f77934a Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Tue, 16 Apr 2024 19:37:04 +0100 Subject: [PATCH 17/19] Refactor test so that a user with known email and first name is only created (or reused) in the one test. Anon users are created for other tests. --- tests/phpunit/Admin/OptIn/EmailOptInTest.php | 21 +++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/phpunit/Admin/OptIn/EmailOptInTest.php b/tests/phpunit/Admin/OptIn/EmailOptInTest.php index 6f06fc37..adb7982f 100644 --- a/tests/phpunit/Admin/OptIn/EmailOptInTest.php +++ b/tests/phpunit/Admin/OptIn/EmailOptInTest.php @@ -28,13 +28,7 @@ class EmailOptInTest extends WP_UnitTestCase { * Set up the user for tests. */ protected function setUp(): void { - $this->current_user_id = $this->factory()->user->create( - array( - 'role' => 'administrator', - 'user_email' => self::TEST_USER_EMAIL, - 'first_name' => self::TEST_USER_FIRST_NAME, - ) - ); + $this->current_user_id = $this->factory()->user->create(); if ( is_wp_error( $this->current_user_id ) ) { $this->fail( $this->current_user_id->get_error_message() ); @@ -120,6 +114,19 @@ public function test_form_renders_modal() { * Test that the form renders and contains the expected email and user first name. */ public function test_form_renders_and_contains_expected_email_and_user_name() { + $maybe_existing_user = get_user_by( 'email', self::TEST_USER_EMAIL ); + // set the users first name to test that it is pre-filled in the form. + if ( $maybe_existing_user && ! get_user_meta( $maybe_existing_user->ID, 'first_name', true ) ) { + update_user_meta( $maybe_existing_user->ID, 'first_name', self::TEST_USER_FIRST_NAME ); + } + + $user_id = $maybe_existing_user ? $maybe_existing_user->ID : $this->factory()->user->create( + array( + 'user_email' => self::TEST_USER_EMAIL, + 'first_name' => self::TEST_USER_FIRST_NAME, + ) + ); + wp_set_current_user( $user_id ); $email_opt_in = new Email_Opt_In(); $email_opt_in->render_form(); $this->expectOutputRegex( '/.*?value="' . self::TEST_USER_EMAIL . '".*?/' ); From 21ad9b608a1f8acde5237aec1bb4bfc1fc884efe Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Wed, 17 Apr 2024 22:02:52 +0100 Subject: [PATCH 18/19] Add aria-hidden to the icon in thickbox close button --- src/emailOptIn/modal.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/emailOptIn/modal.js b/src/emailOptIn/modal.js index 7967d010..323a1a95 100644 --- a/src/emailOptIn/modal.js +++ b/src/emailOptIn/modal.js @@ -18,9 +18,11 @@ export const initOptInModal = () => { // a small delay is needed to ensure the modal is fully loaded before creating a focus trap. setTimeout( function() { - document.getElementById( 'TB_window' ).querySelector( 'input' ).focus(); + const modal = document.getElementById( 'TB_window' ) + .querySelector( '.tb-close-icon' ) + .setAttribute( 'aria-hidden', 'true' ); - const focusTrap = createFocusTrap( '#TB_window' ); + const focusTrap = createFocusTrap( modal ); focusTrap.activate(); jQuery( document ).off( 'tb_unload' ).on( From 3d9eeda4c62f822fde8c03e4ff34958b37971ef2 Mon Sep 17 00:00:00 2001 From: pattonwebz Date: Wed, 17 Apr 2024 22:03:26 +0100 Subject: [PATCH 19/19] Run on tb_unload only `one` time instead of `on` ever fire --- src/emailOptIn/modal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/emailOptIn/modal.js b/src/emailOptIn/modal.js index 323a1a95..cbc31c77 100644 --- a/src/emailOptIn/modal.js +++ b/src/emailOptIn/modal.js @@ -25,7 +25,7 @@ export const initOptInModal = () => { const focusTrap = createFocusTrap( modal ); focusTrap.activate(); - jQuery( document ).off( 'tb_unload' ).on( + jQuery( document ).one( 'tb_unload', function() { onModalClose( focusTrap );