From 76c7cce2ea5a929f8ccfc12f1d07108e143821d4 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Mon, 6 Jul 2026 16:54:19 -0500 Subject: [PATCH 01/15] new client level methods meant just to test API request response codes --- includes/class-client.php | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/includes/class-client.php b/includes/class-client.php index ccd48c91..e32647ab 100644 --- a/includes/class-client.php +++ b/includes/class-client.php @@ -273,6 +273,10 @@ public function get_updated_lists_ids( $old_ids_string ) { return $this->get( "contact_lists/list_id_xrefs?sequence_ids={$old_ids_string}", $this->base_args ); } + public function test_connection() { + return $this->test( $this->base_args ); + } + /** * GET method for API requests. * @@ -434,4 +438,41 @@ private function delete( string $endpoint, array $args = [] ) : array { return json_decode( $response['body'], true ); } + + /** + * Method to do a basic API request test, to check on current credentials. + * + * @since NEXT + * + * @param array $args + * @return bool + */ + private function test( array $args = [] ): bool { + + $url = $this->base_url . '/account/user/privileges'; + + $options = [ + /** + * Sets the HTTP timeout, in seconds, for the request. + * + * @since NEXT + * + * @param int $value The timeout limit, in seconds. Defaults to 30. + * @param string $request_url The request URL. + * + * @return int + */ + 'timeout' => apply_filters( 'http_request_timeout', 30, $url ), + 'headers' => $args, + ]; + + $response = wp_safe_remote_get( $url, $options ); + if ( is_wp_error( $response ) ) { + return false; + } + + $code = wp_remote_retrieve_response_code( $response ); + + return 200 === $code; + } } From 666d258267ebe3463e62eeb0d7c18de2ce9841d6 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Mon, 6 Jul 2026 16:54:36 -0500 Subject: [PATCH 02/15] new test section --- includes/class-connect.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/includes/class-connect.php b/includes/class-connect.php index be1bd056..01307a9c 100644 --- a/includes/class-connect.php +++ b/includes/class-connect.php @@ -236,6 +236,15 @@ public function admin_page_display() { + +
+

+ +

+

+ +

+

From 0d54154e311f743ac9f56eda2c0167f9fd65f3ea Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Mon, 6 Jul 2026 16:54:53 -0500 Subject: [PATCH 03/15] ajax callback to test our API creds --- includes/helper-functions.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/includes/helper-functions.php b/includes/helper-functions.php index e1e12bfd..aa6278d9 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -861,3 +861,16 @@ function constant_contact_get_issued_expired_access_token_times() { 'expires' => $obj->format( 'Y-m-d, H:i' ), ]; }; + +function constant_contact_test_api_ajax_handler(): bool { + if ( ! wp_verify_nonce( $_REQUEST['ctct-test-connection-nonce'], 'ctct-test-connection' ) ) { + wp_send_json_error( [ 'nonce-result' => 'failed' ] ); + exit(); + } + $is_connected = constant_contact()->get_api()->cc()->test_connection(); + $is_connected ? + wp_send_json_success( [ 'is_connected' => 'did connect' ], 200 ) : + wp_send_json_success( [ 'is_connected' => 'did not connect' ], 200 ); + exit(); +} +add_action( 'wp_ajax_constant_contact_test_api_ajax_handler', 'constant_contact_test_api_ajax_handler' ); From b0c758802c9263a1d70411d8755a46231545f67d Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Mon, 6 Jul 2026 16:55:18 -0500 Subject: [PATCH 04/15] js ajax handler --- assets/js/ctct-plugin-admin/ajax.js | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/assets/js/ctct-plugin-admin/ajax.js b/assets/js/ctct-plugin-admin/ajax.js index fbf44d8b..4719834d 100644 --- a/assets/js/ctct-plugin-admin/ajax.js +++ b/assets/js/ctct-plugin-admin/ajax.js @@ -12,6 +12,7 @@ window.CTCTAJAX = {}; // Trigger any field modifications we need to do. that.handleReviewAJAX(); + that.handleAPITest(); }; // Handle saving the decision regarding the review prompt admin notice. @@ -50,5 +51,35 @@ window.CTCTAJAX = {}; } }; + that.handleAPITest = () => { + const apitestlink = document.querySelector('#ctct-test-api'); + if (apitestlink) { + apitestlink.addEventListener('click', (e) => { + e.preventDefault(); + + const data = new FormData(); + data.append('action', 'constant_contact_test_api_ajax_handler'); + + const params = new URLSearchParams(e.target.href); + + if (params.get('ctct-test-connection')) { + data.append('ctct-test-connection-nonce', params.get('ctct-test-connection')); + } + + fetch(window.ajaxurl, options = { + method: 'POST', body: data, + }) + .then((response) => response.json()) + .then((response) => { + if (response.success) { + document.querySelector('#ctct-test-api-result').innerHTML = response.data.is_connected; + } + }).catch((error) => { + console.log(error); + }); + }); + } + }; + that.init(); }(window, window.CTCTAJAX)); From 4ba9da276942af2d894d7856cb7b72254a391afe Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:09:55 -0500 Subject: [PATCH 05/15] adjust our DOM mods, utilize classes for success/error visual changes --- assets/js/ctct-plugin-admin/ajax.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/assets/js/ctct-plugin-admin/ajax.js b/assets/js/ctct-plugin-admin/ajax.js index 4719834d..ed471082 100644 --- a/assets/js/ctct-plugin-admin/ajax.js +++ b/assets/js/ctct-plugin-admin/ajax.js @@ -71,8 +71,13 @@ window.CTCTAJAX = {}; }) .then((response) => response.json()) .then((response) => { + const successDOM = document.querySelector('#ctct-test-api-result'); + successDOM.innerHTML = response.data.is_connected; + successDOM.setAttribute('class', ''); if (response.success) { - document.querySelector('#ctct-test-api-result').innerHTML = response.data.is_connected; + successDOM.classList.add('success'); + } else { + successDOM.classList.add('no-success'); } }).catch((error) => { console.log(error); From 91c30064edffb05e18f3a9db9fe83027e7cffedb Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:10:14 -0500 Subject: [PATCH 06/15] new dark variations for contrast --- assets/sass/_variables.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/assets/sass/_variables.scss b/assets/sass/_variables.scss index 43545af6..944d6dda 100644 --- a/assets/sass/_variables.scss +++ b/assets/sass/_variables.scss @@ -23,7 +23,9 @@ $color-black-translucent: rgba(0,0,0, 0.5); // Theming $color-error: $color-red; +$color-error-dark: #a90b00; $color-success: $color-green; +$color-success-dark: #1c7727; // constant contact colors $color-sky-blue: #88c5e2; From bef83d6bc724a3511a9855d8d031fe04ce8d8be1 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:10:40 -0500 Subject: [PATCH 07/15] remove unneeded styles affecting visuals in admin --- assets/sass/_admin-connect.scss | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/assets/sass/_admin-connect.scss b/assets/sass/_admin-connect.scss index 8ad0a098..4dccf531 100644 --- a/assets/sass/_admin-connect.scss +++ b/assets/sass/_admin-connect.scss @@ -316,8 +316,7 @@ } // Connection Details -.ctct-connected-wrap{ - +.ctct-connected-wrap { .ctct-connection-details{ display: flex; text-align: left; @@ -343,10 +342,6 @@ } p{ - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - &.ctct-label{ justify-content: flex-end; } From c1434a1c86f97f53288eedee5c8592e819870467 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:10:59 -0500 Subject: [PATCH 08/15] move styles to stylesheet rather than inline. success/error styling --- assets/sass/_admin-connect.scss | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/assets/sass/_admin-connect.scss b/assets/sass/_admin-connect.scss index 4dccf531..7fc7040d 100644 --- a/assets/sass/_admin-connect.scss +++ b/assets/sass/_admin-connect.scss @@ -347,6 +347,18 @@ } } } + + #ctct-test-api-result { + font-weight: bold; + margin-left: 10px; + &.success { + color: variables.$color-success-dark; + } + + &.no-success { + color: variables.$color-error-dark; + } + } } // Getting Started/ Next Steps From afb7078a31053a9a6b1ea099285e729e0c0450e2 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:11:15 -0500 Subject: [PATCH 09/15] remove inline styles --- includes/class-connect.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-connect.php b/includes/class-connect.php index 01307a9c..3ffb70e6 100644 --- a/includes/class-connect.php +++ b/includes/class-connect.php @@ -242,7 +242,7 @@ public function admin_page_display() {

- +

From ba00e722079e5f4e06f618031726a91a6abadff6 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:11:44 -0500 Subject: [PATCH 10/15] switch to json error to indicate no connection. --- includes/helper-functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/helper-functions.php b/includes/helper-functions.php index aa6278d9..4c5d4e7e 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -870,7 +870,7 @@ function constant_contact_test_api_ajax_handler(): bool { $is_connected = constant_contact()->get_api()->cc()->test_connection(); $is_connected ? wp_send_json_success( [ 'is_connected' => 'did connect' ], 200 ) : - wp_send_json_success( [ 'is_connected' => 'did not connect' ], 200 ); + wp_send_json_error( [ 'is_connected' => 'did not connect' ], 200 ); exit(); } add_action( 'wp_ajax_constant_contact_test_api_ajax_handler', 'constant_contact_test_api_ajax_handler' ); From dd4b01f4ba6cf013ac1f1dd910e0a33eebec46fa Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:18:39 -0500 Subject: [PATCH 11/15] reorder data, reword status to action --- includes/class-connect.php | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/includes/class-connect.php b/includes/class-connect.php index 3ffb70e6..d8303d5c 100644 --- a/includes/class-connect.php +++ b/includes/class-connect.php @@ -157,7 +157,7 @@ public function admin_page_display() { if ( constant_contact_get_needs_manual_reconnect() ) { $heading = esc_html__( 'Manual reconnection required', 'constant-contact-forms' ); $description = esc_html__( 'Issues with reauthentication for tokens occurred and a manual disconnect and reconnect is needed. Use the status button to start the re-authentication process.', 'constant-contact-forms' ); - $btn_value = esc_attr__( 'Disconnected', 'constant-contact-forms' ); + $btn_value = esc_attr__( 'Force disconnect', 'constant-contact-forms' ); } $times = constant_contact_get_issued_expired_access_token_times(); ?> @@ -228,7 +228,15 @@ public function admin_page_display() {

- + +

+

+ +

+
+
+

+

@@ -236,15 +244,6 @@ public function admin_page_display() {
- -
-

- -

-

- -

-

From 2e167df4607f330adbf83a40b65fb4ae69852419 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:22:42 -0500 Subject: [PATCH 12/15] early exit if cannot manage options --- includes/helper-functions.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/includes/helper-functions.php b/includes/helper-functions.php index 4c5d4e7e..28e932b0 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -863,10 +863,16 @@ function constant_contact_get_issued_expired_access_token_times() { }; function constant_contact_test_api_ajax_handler(): bool { + + if ( ! current_user_can( 'manage_options' ) ) { + exit(); + } + if ( ! wp_verify_nonce( $_REQUEST['ctct-test-connection-nonce'], 'ctct-test-connection' ) ) { wp_send_json_error( [ 'nonce-result' => 'failed' ] ); exit(); } + $is_connected = constant_contact()->get_api()->cc()->test_connection(); $is_connected ? wp_send_json_success( [ 'is_connected' => 'did connect' ], 200 ) : From a7bd9a998771c2b929454012176aa507841b48cd Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:23:50 -0500 Subject: [PATCH 13/15] fill in version string --- includes/class-client.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/class-client.php b/includes/class-client.php index e32647ab..2d3213c9 100644 --- a/includes/class-client.php +++ b/includes/class-client.php @@ -442,7 +442,7 @@ private function delete( string $endpoint, array $args = [] ) : array { /** * Method to do a basic API request test, to check on current credentials. * - * @since NEXT + * @since 2.22.0 * * @param array $args * @return bool @@ -455,7 +455,7 @@ private function test( array $args = [] ): bool { /** * Sets the HTTP timeout, in seconds, for the request. * - * @since NEXT + * @since 2.22.0 * * @param int $value The timeout limit, in seconds. Defaults to 30. * @param string $request_url The request URL. From f9f435567cc30d06f405c587762147328b763023 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:25:42 -0500 Subject: [PATCH 14/15] fill in missed at since tag --- includes/helper-functions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/helper-functions.php b/includes/helper-functions.php index 28e932b0..9a67472c 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -841,7 +841,7 @@ function constant_contact_get_date_field_order( $format = '' ) { /** * Return an array of timestamps for issued, current, and expected expiration time for current access token. * - * @since NEXT + * @since 2.21.0 * * @return array|null * @throws Exception From 8fe3451d400bda4381b8d4fe70917338c73769ab Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Tue, 7 Jul 2026 11:26:01 -0500 Subject: [PATCH 15/15] phpdoc for new ajax handler --- includes/helper-functions.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/includes/helper-functions.php b/includes/helper-functions.php index 9a67472c..f1a1a456 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -862,6 +862,15 @@ function constant_contact_get_issued_expired_access_token_times() { ]; }; +/** + * Make a test API request to a general endpoint. + * + * Meant to just verify access tokens are still valid. + * + * @since 2.22.0 + * + * @return bool + */ function constant_contact_test_api_ajax_handler(): bool { if ( ! current_user_can( 'manage_options' ) ) {