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() {
+
-
-
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' ) ) {