From cf78ad46c2c4271b8bb595a5613513207a9963fd Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Thu, 19 Mar 2026 10:19:18 -0500 Subject: [PATCH 1/6] re-merge API utility updates so far --- constant-contact-forms.php | 19 ++- includes/class-admin-pages.php | 2 +- includes/class-api-utility.php | 166 ++++++++++++++++++++++++ includes/class-api.php | 119 ----------------- includes/class-builder.php | 2 +- includes/class-connect.php | 2 +- includes/class-notification-content.php | 2 +- 7 files changed, 188 insertions(+), 124 deletions(-) create mode 100644 includes/class-api-utility.php diff --git a/constant-contact-forms.php b/constant-contact-forms.php index d6af4927..9b915ed1 100644 --- a/constant-contact-forms.php +++ b/constant-contact-forms.php @@ -309,6 +309,13 @@ class Constant_Contact { */ private ConstantContact_Health $health; + /** + * An instance of the ConstantContact_API_Utility class. + * @since NEXT + * @var ConstantContact_API_Utility + */ + private ConstantContact_API_Utility $utility; + /** * Option name for where we store the timestamp of when the plugin was activated. * @@ -395,7 +402,8 @@ public function minimum_version() { * @since 1.0.0 */ public function plugin_classes() { - $this->api = new ConstantContact_API( $this ); + $this->utility = new ConstantContact_API_Utility( $this ); + $this->api = new ConstantContact_API( $this ); if ( class_exists( 'FLBuilder' ) ) { // Load if Beaver Builder is active. $this->beaver_builder = new ConstantContact_Beaver_Builder( $this ); @@ -848,6 +856,15 @@ public function get_updates(): ConstantContact_Updates { return $this->updates; } + /** + * API Utility getter. + * @return ConstantContact_API_Utility + * @since NEXT + */ + public function get_api_utility(): ConstantContact_API_Utility { + return $this->utility; + } + /** * Include a file from the classes directory. * diff --git a/includes/class-admin-pages.php b/includes/class-admin-pages.php index 82a2dd0f..dfeca08b 100644 --- a/includes/class-admin-pages.php +++ b/includes/class-admin-pages.php @@ -66,7 +66,7 @@ public function about_page() { $new_link = ''; if ( ! constant_contact()->get_api()->is_connected() ) { - $new_link = constant_contact()->get_api()->get_signup_link(); + $new_link = constant_contact()->get_api_utility()->get_signup_link(); $auth_link = admin_url( 'edit.php?post_type=ctct_forms&page=ctct_options_connect' ); } ?> diff --git a/includes/class-api-utility.php b/includes/class-api-utility.php new file mode 100644 index 00000000..b9d8ba00 --- /dev/null +++ b/includes/class-api-utility.php @@ -0,0 +1,166 @@ +plugin = $plugin; + } + + /** + * Obfuscate the left side of email addresses at the `@`. + * + * @since 1.7.0 + * @since NEXT Moved to utility class. + * + * @param array $contact Contact data. + * @return array + */ + public function clear_email( array $contact ): array { + $clean = []; + foreach ( $contact as $contact_key => $contact_value ) { + if ( is_array( $contact_value ) ) { + $clean[ $contact_key ] = $this->clear_email( $contact_value ); + } elseif ( is_email( $contact_value ) ) { + $email_parts = explode( '@', $contact_value ); + $clean[ $contact_key ] = implode( '@', [ '***', $email_parts[1] ] ); + } else { + $clean[ $contact_key ] = $contact_value; + } + } + return $clean; + } + + /** + * Obfuscate phone numbers. + * + * @since 1.13.0 + * @since NEXT Moved to utility class. + * + * @param array $contact Contact data. + * @return array + */ + public function clear_phone( array $contact ): array { + $clean = $contact; + foreach ( $contact as $contact_key => $contact_value ) { + if ( is_array( $contact_value ) && ! empty( $contact_value['key'] ) && $contact_value['key'] === 'phone_number' ) { + $clean[ $contact_key ]['val'] = '***-***-****'; + } + } + + return $clean; + } + + /** + * Remove hCaptcha data from logged data. + * + * @since 2.9.0 + * @since NEXT Moved to utility class. + * + * @param array $contact Contact data. + * @return array + */ + public function clear_hcaptcha( array $contact ): array { + if ( array_key_exists( 'h-captcha-response', $contact ) ) { + unset( $contact['h-captcha-response'] ); + } + + return $contact; + } + + /** + * Pushes all error to api_error_message. + * + * @since 1.0.0 + * @since NEXT Moved to utility class. + * + * @throws Exception Throws Exception if encountered while attempting to log errors. + * + * @param array $errors Errors from API. + */ + public function log_errors( $errors ): void { + if ( is_array( $errors ) ) { + foreach ( $errors as $error ) { + constant_contact_maybe_log_it( + 'API', + $error + ); + } + } + } + + /** + * Helper method to output a link for our connect modal. + * + * @since 1.0.0 + * @since NEXT Moved to utility class. + * + * @return string Signup URL. + */ + public function get_signup_link(): string { + return 'https://www.constantcontact.com/signup'; + } + + + /** + * Base64 encode URL. + * + * @since 2.0.0 + * @since NEXT Moved to utility class. + * + * @param string $data + * + * @return string + */ + public function base64url_encode( string $data ): string { + return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' ); + } + + + /** + * Obfuscate a value in our debug logs. + * + * Helps keep things private and not put into a potentially publicly accessed file. + * + * @since 2.1.0 + * @since NEXT Moved to utility class. + * + * @param string $data_item Item to obfuscate. + * + * @return string + */ + public function obfuscate_api_data_item( string $data_item ): string { + $start = substr( $data_item, 0, 8 ); + return $start . '***'; + } +} diff --git a/includes/class-api.php b/includes/class-api.php index 618128aa..79ba26f1 100644 --- a/includes/class-api.php +++ b/includes/class-api.php @@ -871,65 +871,6 @@ public function add_contact( $new_contact = [], $form_id = 0 ) { return $return_contact; } - /** - * Obfuscate the left side of email addresses at the `@`. - * - * @since 1.7.0 - * - * @param array $contact Contact data. - * @return array - */ - private function clear_email( array $contact ) { - $clean = []; - foreach ( $contact as $contact_key => $contact_value ) { - if ( is_array( $contact_value ) ) { - $clean[ $contact_key ] = $this->clear_email( $contact_value ); - } elseif ( is_email( $contact_value ) ) { - $email_parts = explode( '@', $contact_value ); - $clean[ $contact_key ] = implode( '@', [ '***', $email_parts[1] ] ); - } else { - $clean[ $contact_key ] = $contact_value; - } - } - return $clean; - } - - /** - * Obfuscate phone numbers. - * - * @author Scott Anderson - * @since 1.13.0 - * - * @param array $contact Contact data. - * @return array - */ - private function clear_phone( array $contact ) { - $clean = $contact; - foreach ( $contact as $contact_key => $contact_value ) { - if ( is_array( $contact_value ) && ! empty( $contact_value['key'] ) && $contact_value['key'] === 'phone_number' ) { - $clean[ $contact_key ]['val'] = '***-***-****'; - } - } - - return $clean; - } - - /** - * Remove hCaptcha data from logged data. - * - * @since 2.9.0 - * - * @param array $contact Contact data. - * @return array - */ - private function clear_hcaptcha( array $contact ) { - if ( array_key_exists( 'h-captcha-response', $contact ) ) { - unset( $contact['h-captcha-response'] ); - } - - return $contact; - } - /** * Helper method to update contact. * @@ -1165,26 +1106,6 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated return $contact; } - /** - * Pushes all error to api_error_message. - * - * @since 1.0.0 - * - * @throws Exception Throws Exception if encountered while attempting to log errors. - * - * @param array $errors Errors from API. - */ - public function log_errors( $errors ) { - if ( is_array( $errors ) ) { - foreach ( $errors as $error ) { - constant_contact_maybe_log_it( - 'API', - $error - ); - } - } - } - /** * Make sure we don't over-do API requests, helper method to check if we're connected. * @@ -1219,17 +1140,6 @@ public function get_settings_link( $settings_tab = 'ctct_options_settings_genera ); } - /** - * Helper method to output a link for our connect modal. - * - * @since 1.0.0 - * - * @return string Signup URL. - */ - public function get_signup_link() { - return 'https://www.constantcontact.com/signup'; - } - /** * Maybe get the disclosure address from the API Organization Information. * @@ -1317,19 +1227,6 @@ private function code_challenge( ?string $code_verifier = null ): array { return [ $code, $this->base64url_encode( pack( 'H*', hash( 'sha256', $code ) ) ) ]; } - /** - * Base64 encode URL. - * - * @since 2.0.0 - * - * @param string $data - * - * @return string - */ - private function base64url_encode( string $data ): string { - return rtrim( strtr( base64_encode( $data ), '+/', '-_' ), '=' ); - } - /** * Handle user session details. * @@ -1667,22 +1564,6 @@ private function exec( $url, $options ): bool { return false; } - /** - * Obfuscate a value in our debug logs. - * - * Helps keep things private and not put into a potentially publicly accessed file. - * - * @since 2.1.0 - * - * @param string $data_item Item to obfuscate. - * - * @return string - */ - private function obfuscate_api_data_item( string $data_item ): string { - $start = substr( $data_item, 0, 8 ); - return $start . '***'; - } - /** * Check if a submission has note data in place. * diff --git a/includes/class-builder.php b/includes/class-builder.php index 2788776e..c2324206 100644 --- a/includes/class-builder.php +++ b/includes/class-builder.php @@ -313,7 +313,7 @@ class="ctct-modal-flare"

-
+
diff --git a/includes/class-connect.php b/includes/class-connect.php index 247cf446..9040829e 100644 --- a/includes/class-connect.php +++ b/includes/class-connect.php @@ -307,7 +307,7 @@ public function admin_page_display() {

- + diff --git a/includes/class-notification-content.php b/includes/class-notification-content.php index 92cff866..28099e00 100644 --- a/includes/class-notification-content.php +++ b/includes/class-notification-content.php @@ -53,7 +53,7 @@ public static function activation(): string { admin_url( 'edit.php' ) ); $auth_url = wp_nonce_url( $auth_url, 'ctct-user-is-dismissing', 'ctct-dismiss' ); - $try_url = constant_contact()->get_api()->get_signup_link(); + $try_url = constant_contact()->get_api_utility()->get_signup_link(); if ( ! empty( $_GET['page'] ) && 'ctct_options_connect' === sanitize_text_field( $_GET['page'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return ''; From be195e616fdfe71be70c1916b0cede88201a1e67 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Thu, 19 Mar 2026 10:23:23 -0500 Subject: [PATCH 2/6] handle api class and moved previous methods --- includes/class-api.php | 64 +++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/includes/class-api.php b/includes/class-api.php index 79ba26f1..1db5725d 100644 --- a/includes/class-api.php +++ b/includes/class-api.php @@ -339,7 +339,7 @@ public function get_account_info() { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -351,7 +351,7 @@ public function get_account_info() { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } } @@ -388,7 +388,7 @@ public function get_contacts() { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -400,7 +400,7 @@ public function get_contacts() { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } } @@ -448,7 +448,7 @@ public function get_lists( bool $force_skip_cache = false ) { add_filter( 'constant_contact_force_logging', '__return_true' ); $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . $results['error_key'] . ': ' . $results['error_message']; - $this->log_errors($our_errors); + constant_contact()->get_api_utility()->log_errors($our_errors); constant_contact_forms_maybe_set_exception_notice(); } } catch ( CtctException $ex ) { @@ -456,7 +456,7 @@ public function get_lists( bool $force_skip_cache = false ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -468,7 +468,7 @@ public function get_lists( bool $force_skip_cache = false ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } } @@ -510,7 +510,7 @@ public function get_v2_list_id_x_refs( string $old_ids_string, bool $force_skip_ $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -522,7 +522,7 @@ public function get_v2_list_id_x_refs( string $old_ids_string, bool $force_skip_ $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } } @@ -565,7 +565,7 @@ public function get_list( string $id ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -577,7 +577,7 @@ public function get_list( string $id ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } } @@ -613,7 +613,7 @@ public function add_list( $new_list = [] ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -625,7 +625,7 @@ public function add_list( $new_list = [] ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } if ( ! isset( $list[0]['error_key'] ) ) { @@ -657,7 +657,7 @@ public function add_list( $new_list = [] ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -669,7 +669,7 @@ public function add_list( $new_list = [] ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } return $return_list; @@ -714,7 +714,7 @@ public function update_list( array $updated_list = [] ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -726,7 +726,7 @@ public function update_list( array $updated_list = [] ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } return $return_list; @@ -759,7 +759,7 @@ public function delete_list( array $updated_list = [] ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -771,7 +771,7 @@ public function delete_list( array $updated_list = [] ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } return $list; @@ -835,7 +835,7 @@ public function add_contact( $new_contact = [], $form_id = 0 ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -847,7 +847,7 @@ public function add_contact( $new_contact = [], $form_id = 0 ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } if ( @@ -862,9 +862,9 @@ public function add_contact( $new_contact = [], $form_id = 0 ) { true ) ) { - $new_contact = $this->clear_email( $new_contact ); - $new_contact = $this->clear_phone( $new_contact ); - $new_contact = $this->clear_hcaptcha( $new_contact ); + $new_contact = constant_contact()->get_api_utility()->clear_email( $new_contact ); + $new_contact = constant_contact()->get_api_utility()->clear_phone( $new_contact ); + $new_contact = constant_contact()->get_api_utility()->clear_hcaptcha( $new_contact ); constant_contact_maybe_log_it( 'API', 'Submitted contact data', $new_contact ); } @@ -906,7 +906,7 @@ public function create_update_contact( $list, $email, $user_data, $form_id ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors = $ex->getErrors(); $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); constant_contact_forms_maybe_set_exception_notice( $ex ); } catch ( Exception $ex ) { $error = new stdClass(); @@ -918,7 +918,7 @@ public function create_update_contact( $list, $email, $user_data, $form_id ) { $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - $this->log_errors( $our_errors ); + constant_contact()->get_api_utility()->log_errors( $our_errors ); } $new_contact = $this->cc()->create_update_contact( @@ -1086,7 +1086,7 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated $errors = []; $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); $errors[] = $extra . $e->getErrors(); - $this->log_errors( $errors ); + constant_contact()->get_api_utility()->log_errors( $errors ); constant_contact_forms_maybe_set_exception_notice( $e ); break; } @@ -1224,7 +1224,7 @@ private function code_challenge( ?string $code_verifier = null ): array { return [ '', '' ]; } - return [ $code, $this->base64url_encode( pack( 'H*', hash( 'sha256', $code ) ) ) ]; + return [ $code, constant_contact()->get_api_utility()->base64url_encode( pack( 'H*', hash( 'sha256', $code ) ) ) ]; } /** @@ -1530,8 +1530,8 @@ private function exec( $url, $options ): bool { if ( ! empty( $data['access_token'] ) ) { - constant_contact_maybe_log_it( 'Refresh Token: ', 'Old Refresh Token: ' . $this->obfuscate_api_data_item( $this->refresh_token ) ); - constant_contact_maybe_log_it( 'Access Token: ', 'Old Access Token: ' . $this->obfuscate_api_data_item( $this->access_token ) ); + constant_contact_maybe_log_it( 'Refresh Token: ', 'Old Refresh Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->refresh_token ) ); + constant_contact_maybe_log_it( 'Access Token: ', 'Old Access Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->access_token ) ); constant_contact()->get_connect()->e_set( '_ctct_access_token', $data['access_token'] ); constant_contact()->get_connect()->e_set( '_ctct_refresh_token', $data['refresh_token'] ); @@ -1546,8 +1546,8 @@ private function exec( $url, $options ): bool { $expDateObj = $dateObj->modify( '+' . $data['expires_in'] . ' seconds' ); constant_contact_maybe_log_it( 'Refresh Token: ', 'Refresh token successfully received' ); - constant_contact_maybe_log_it( 'Refresh Token: ', 'New Refresh Token: ' . $this->obfuscate_api_data_item( $this->refresh_token ) ); - constant_contact_maybe_log_it( 'Access Token: ', 'New Access Token: ' . $this->obfuscate_api_data_item( $this->access_token ) ); + constant_contact_maybe_log_it( 'Refresh Token: ', 'New Refresh Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->refresh_token ) ); + constant_contact_maybe_log_it( 'Access Token: ', 'New Access Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->access_token ) ); constant_contact_maybe_log_it( 'Expiration time:', 'Current time: ' . $dateObj->format( 'Y-n-d, H:i' ) . ' Estimated expiration time: ' . $expDateObj->format( 'Y-n-d, H:i' ) From b1edcc8f6baaa1210960bb79e6ea84d321a9acf7 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Thu, 19 Mar 2026 10:23:47 -0500 Subject: [PATCH 3/6] move previously deprecated function to appropriate file --- includes/class-api.php | 13 ------------- includes/deprecated.php | 12 ++++++++++++ 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/includes/class-api.php b/includes/class-api.php index 1db5725d..477ab8dd 100644 --- a/includes/class-api.php +++ b/includes/class-api.php @@ -1765,16 +1765,3 @@ public function set_email_type() { return 'text/html'; } } - -/** - * Helper function to get/return the ConstantContact_API object. - * - * @since 1.0.0 - * @deprecated 2.11.0 - * - * @return object ConstantContact_API - */ -function constantcontact_api() { - _deprecated_function( __FUNCTION__, '2.11.0', 'constant_contact()->get_api()' ); - return constant_contact()->get_api(); -} diff --git a/includes/deprecated.php b/includes/deprecated.php index fd3dec55..91e14c7c 100644 --- a/includes/deprecated.php +++ b/includes/deprecated.php @@ -58,3 +58,15 @@ function ctct_has_forms() { return constant_contact_has_forms(); } + +/** + * Helper function to get/return the ConstantContact_API object. + * @return object ConstantContact_API + * @since 1.0.0 + * @deprecated 2.11.0 + */ +function constantcontact_api() { + _deprecated_function( __FUNCTION__, '2.11.0', 'constant_contact()->get_api()' ); + + return constant_contact()->get_api(); +} From 2b1d4da96cf4ffc7103545febf9d0e151f15c4cf Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Thu, 19 Mar 2026 10:24:19 -0500 Subject: [PATCH 4/6] misc phpcs --- constant-contact-forms.php | 59 ++++++++++++++++----------------- includes/notification-logic.php | 16 ++++----- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/constant-contact-forms.php b/constant-contact-forms.php index 9b915ed1..9561a638 100644 --- a/constant-contact-forms.php +++ b/constant-contact-forms.php @@ -45,9 +45,9 @@ * @param string $class_name Name of the class being requested. * @return null */ -function constant_contact_autoload_classes( $class_name ) { +function constant_contact_autoload_classes( string $class_name ) { if ( ! str_starts_with( $class_name, 'ConstantContact_' ) ) { - return; + return null; } $filename = strtolower( @@ -59,6 +59,8 @@ function constant_contact_autoload_classes( $class_name ) { ); Constant_Contact::include_file( $filename ); + + return null; } spl_autoload_register( 'constant_contact_autoload_classes' ); @@ -340,7 +342,7 @@ class Constant_Contact { * * @return Constant_Contact A single instance of this class. */ - public static function get_instance() { + public static function get_instance(): Constant_Contact { if ( null === self::$single_instance ) { self::$single_instance = new self(); } @@ -392,7 +394,7 @@ protected function __construct() { * * @since 1.0.1 */ - public function minimum_version() { + public function minimum_version(): void { echo '

' . esc_html__( 'Constant Contact Forms requires PHP 7.4 or higher. Your hosting provider or website administrator should be able to assist in updating your PHP version.', 'constant-contact-forms' ) . '

'; } @@ -437,7 +439,7 @@ public function plugin_classes() { * * @since 1.0.0 */ - public function admin_plugin_classes() { + public function admin_plugin_classes(): void { $this->admin = new ConstantContact_Admin( $this, $this->basename ); $this->admin_pages = new ConstantContact_Admin_Pages( $this ); $this->block = new ConstantContact_Block( $this ); @@ -450,7 +452,7 @@ public function admin_plugin_classes() { * * @return void */ - public function hooks() { + public function hooks(): void { if ( ! $this->meets_php_requirements() ) { add_action( 'admin_notices', [ $this, 'minimum_version' ] ); return; @@ -479,7 +481,7 @@ public function hooks() { * * @since 1.0.0 */ - public function activate() { + public function activate(): void { update_option( self::$activated_date_option, time() ); } @@ -490,7 +492,7 @@ public function activate() { * * @return void */ - public function deactivate() { + public function deactivate(): void { if ( ! $this->meets_php_requirements() ) { return; @@ -519,7 +521,7 @@ public function deactivate() { * * @since 1.6.0 */ - public function uninstall() { + public function uninstall(): void { $uninstaller = new ConstantContact_Uninstall(); $uninstaller->run(); } @@ -540,7 +542,7 @@ public function meets_php_requirements() : bool { * * @since 1.0.0 */ - public function init() { + public function init(): void { $this->init_debug_log(); } @@ -551,7 +553,7 @@ public function init() { * * @return void */ - protected function init_debug_log() { + protected function init_debug_log(): void { if ( ! constant_contact_debugging_enabled() ) { return; @@ -566,7 +568,7 @@ protected function init_debug_log() { * * @since 1.0.0 */ - public function load_libs() { + public function load_libs(): void { // Load what we can, automagically. require_once $this->dir( 'vendor_prefixed/autoload.php' ); @@ -618,16 +620,11 @@ public function ajax_save_clear_first_form() { * @return mixed */ public function __get( $field ) { - switch ( $field ) { - case 'version': - return self::VERSION; - case 'basename': - case 'path': - case 'url': - return $this->$field; - default: - throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $field ); - } + return match ( $field ) { + 'version' => self::VERSION, + 'basename', 'path', 'url' => $this->$field, + default => throw new Exception( 'Invalid ' . __CLASS__ . ' property: ' . $field ), + }; } /** @@ -874,7 +871,7 @@ public function get_api_utility(): ConstantContact_API_Utility { * @param bool $include_class Whether or ot to include the class. * @return bool Result of include call. */ - public static function include_file( string $filename, bool $include_class = true ) { + public static function include_file( string $filename, bool $include_class = true ): bool { // By default, all files are named 'class-something.php'. if ( $include_class ) { @@ -900,7 +897,7 @@ public static function include_file( string $filename, bool $include_class = tru */ public static function dir( string $path = '' ) : string { static $dir; - $dir = $dir ? $dir : trailingslashit( __DIR__ ); + $dir = $dir ?: trailingslashit( __DIR__ ); return $dir . $path; } @@ -914,7 +911,7 @@ public static function dir( string $path = '' ) : string { */ public static function url( string $path = '' ) : string { static $url; - $url = $url ? $url : trailingslashit( plugin_dir_url( __FILE__ ) ); + $url = $url ?: trailingslashit( plugin_dir_url( __FILE__ ) ); return $url . $path; } @@ -925,7 +922,7 @@ public static function url( string $path = '' ) : string { * * @return string License text. */ - public function get_license_text() { + public function get_license_text(): string { $license = self::url( self::LICENSE_FILE ); $license_content = wp_remote_get( $license ); @@ -944,7 +941,7 @@ public function get_license_text() { * @param array $classes Existing body classes. * @return array Amended body classes. */ - public function body_classes( $classes = [] ) : array { + public function body_classes( array $classes = [] ) : array { $theme = wp_get_theme()->get_template(); $classes[] = "ctct-{$theme}"; // Prefixing for user knowledge of source. @@ -956,7 +953,7 @@ public function body_classes( $classes = [] ) : array { * * @since 1.4.0 */ - public function register_admin_assets() { + public function register_admin_assets(): void { wp_register_style( 'constant-contact-forms-admin', @@ -971,7 +968,7 @@ public function register_admin_assets() { * * @since 1.4.0 */ - public function register_front_assets() { + public function register_front_assets(): void { if ( constant_contact_disable_frontend_css() ) { return; @@ -1030,7 +1027,7 @@ public function is_constant_contact() : bool { * * @return Constant_Contact Singleton instance of plugin class. */ -function constant_contact() { +function constant_contact(): Constant_Contact { return Constant_Contact::get_instance(); } @@ -1039,7 +1036,7 @@ function constant_contact() { * * @since 1.6.0 */ -function constant_contact_uninstall() { +function constant_contact_uninstall(): void { $instance = Constant_Contact::get_instance(); $instance->uninstall(); } diff --git a/includes/notification-logic.php b/includes/notification-logic.php index b0c79f27..62289e97 100644 --- a/includes/notification-logic.php +++ b/includes/notification-logic.php @@ -16,7 +16,7 @@ * @return bool * @since 1.2.2 */ -function constant_contact_maybe_display_review_notification() : bool { +function constant_contact_maybe_display_review_notification(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; @@ -95,7 +95,7 @@ function constant_contact_maybe_display_review_notification() : bool { * @return bool * @since 1.6.0 */ -function constant_contact_maybe_display_exceptions_notice() : bool { +function constant_contact_maybe_display_exceptions_notice(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -110,7 +110,7 @@ function constant_contact_maybe_display_exceptions_notice() : bool { * @return bool Whether to display the deleted forms notice. * @since 1.8.0 */ -function constant_contact_maybe_display_deleted_forms_notice() : bool { +function constant_contact_maybe_display_deleted_forms_notice(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -153,7 +153,7 @@ function constant_contact_forms_maybe_set_exception_notice( $e = '' ) { * @return bool * @since 1.14.0 */ -function constant_contact_maybe_display_api3_upgrade_notice() : bool { +function constant_contact_maybe_display_api3_upgrade_notice(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -168,7 +168,7 @@ function constant_contact_maybe_display_api3_upgrade_notice() : bool { * @return bool|int * @since 2.0.0 */ -function constant_contact_maybe_display_api3_upgraded_notice() : bool { +function constant_contact_maybe_display_api3_upgraded_notice(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -190,7 +190,7 @@ function constant_contact_maybe_display_api3_upgraded_notice() : bool { * @return bool * @since 2.2.0 */ -function constant_contact_maybe_display_disconnect_reconnect_notice() : bool { +function constant_contact_maybe_display_disconnect_reconnect_notice(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -203,7 +203,7 @@ function constant_contact_maybe_display_disconnect_reconnect_notice() : bool { * @return bool * @since 2.2.0 */ -function constant_contact_maybe_show_cron_notification() : bool { +function constant_contact_maybe_show_cron_notification(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } @@ -219,7 +219,7 @@ function constant_contact_maybe_show_cron_notification() : bool { return false; } -function constant_contact_maybe_show_update_available_notification() : bool { +function constant_contact_maybe_show_update_available_notification(): bool { if ( ! current_user_can( 'manage_options' ) ) { return false; } From c513a2d093b700ba9434686a5ba0800614441db5 Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Thu, 19 Mar 2026 10:46:06 -0500 Subject: [PATCH 5/6] re-merge method order reorganization --- includes/class-api.php | 1462 ++++++++++++++++++++-------------------- 1 file changed, 722 insertions(+), 740 deletions(-) diff --git a/includes/class-api.php b/includes/class-api.php index 477ab8dd..43622e3e 100644 --- a/includes/class-api.php +++ b/includes/class-api.php @@ -286,224 +286,361 @@ public function get_api_token() { } /** - * Returns Refresh API token. - * - * @since 2.0.0 + * Exchange an authorization code for an access token. + * Make this call by passing in the code present when the account owner is redirected back to you. + * The response will contain an 'access_token' and 'refresh_token' * - * @param string $type api key type. - * @return string Refresh Token. + * @param array of get parameters passed to redirect URL */ - public function get_refresh_token() { - return $this->refresh_token; + public function acquire_access_token(): bool { + + if ( ! empty( $_POST['action'] ) && 'heartbeat' === sanitize_text_field( $_POST['action'] ) ) { + return false; + } + + if ( ! empty( $_POST['ctct-disconnect'] ) && 'true' === sanitize_text_field( $_POST['ctct-disconnect'] ) ) { + return false; + } + $options = get_option( 'ctct_options_settings' ); + if ( empty( $options ) ) { + return false; + } + + if ( empty( $options['_ctct_form_state_authcode'] ) ) { + return false; + } + + $code_state = $options['_ctct_form_state_authcode']; + + parse_str( $code_state, $parsed_code_state ); + $parsed_code_state = array_values( $parsed_code_state ); + + if ( empty( $parsed_code_state[0] ) || empty( $parsed_code_state[1] ) ) { + $this->status_code = 0; + $this->last_error = 'Invalid state or auth code'; + constant_contact_maybe_log_it( 'Error: ', $this->last_error ); + + return false; + } else { + $code = $parsed_code_state[0]; + $state = $parsed_code_state[1]; + } + + $expected_state = get_option( 'CtctConstantContactState' ); + + if ( ( $state ?? 'undefined' ) != $expected_state ) { + $this->status_code = 0; + $this->last_error = 'state is not correct'; + constant_contact_maybe_log_it( 'Error: ', $this->last_error ); + + return false; + } + // Create full request URL + $body = [ + 'client_id' => $this->client_api_key, + 'code' => $code, + 'redirect_uri' => $this->redirect_URI, + 'grant_type' => 'authorization_code', + ]; + + $body['code_verifier'] = get_option( 'CtctConstantContactcode_verifier' ); + + $headers = $this->set_authorization(); + + $url = $this->oauth2_url; + + $options = [ + 'body' => $body, + 'headers' => $headers, + ]; + + // This will be either true or false. + $result = $this->exec( $url, $options ); + + if ( false === $result ) { + constant_contact_set_needs_manual_reconnect( 'true' ); + } else { + + /** + * Fires after successful access token acquisition. + * @since 2.3.0 + */ + do_action( 'ctct_access_token_acquired' ); + + constant_contact_set_needs_manual_reconnect( 'false' ); + } + + + return $result; } /** - * Info of the connected CTCT account. - * - * @since 1.0.0 - * - * @return array Current connected ctct account info. + * Refresh the access token. + * @return array + * @throws Exception + * @since 2.0.0 */ - public function get_account_info() { + public function refresh_token() { - if ( ! $this->is_connected() ) { - return []; + $status = []; + // Force prevent any further attempts until humans interject. + if ( constant_contact_get_needs_manual_reconnect() ) { + $status['success'] = false; + $status['reason'] = 'manual_reconnect'; + + return $status; } - $acct_data = get_transient( 'constant_contact_acct_info' ); + $token = constant_contact()->get_connect()->e_get( '_ctct_refresh_token' ); + if ( empty( $token ) ) { + $status['success'] = false; + $status['reason'] = 'no available token'; - /** - * Filters whether or not to bypass transient with a filter. - * - * @since 1.0.0 - * - * @param bool $value Whether or not to bypass. - */ - $bypass_acct_cache = apply_filters( 'constant_contact_bypass_acct_info_cache', false ); + return $status; + } - if ( false === $acct_data || $bypass_acct_cache ) { + constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh token triggered' ); - try { - $acct_data = $this->cc()->get_account_info(); - if ( array_key_exists( 'error_key', $acct_data ) && 'unauthorized' === $acct_data['error_key'] ) { - $this->refresh_token(); + // Create full request URL + $body = [ + 'client_id' => $this->client_api_key, + 'refresh_token' => constant_contact()->get_connect()->e_get( '_ctct_refresh_token' ), + 'redirect_uri' => $this->redirect_URI, + 'grant_type' => 'refresh_token', + ]; - $acct_data = $this->cc()->get_account_info(); - } + $url = $this->oauth2_url; + $headers = $this->set_authorization(); - if ( $acct_data && ! array_key_exists( 'error_key', $acct_data ) ) { - set_transient( 'constant_contact_acct_info', $acct_data, 12 * HOUR_IN_SECONDS ); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); + $options = [ + 'body' => $body, + 'headers' => $headers, + ]; - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); + $result = $this->exec( $url, $options ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - } + if ( false === $result ) { + constant_contact_maybe_log_it( 'Refresh Token:', 'Expired. Refresh attempted at ' . current_datetime()->format( 'Y-n-d, H:i' ) ); + constant_contact_set_needs_manual_reconnect( 'true' ); + $status['success'] = false; + $status['reason'] = 'expired'; + } else { + delete_transient( 'ctct_lists' ); + update_option( 'ctct_access_token_timestamp', time() ); + constant_contact_set_needs_manual_reconnect( 'false' ); + + $status['success'] = true; + $status['reason'] = 'refreshed'; } - return $acct_data; + return $status; } /** - * Contacts of the connected CTCT account. - * - * @since 1.0.0 - * - * @return array Current connect ctct account contacts. + * Check if our current access token is expired. + * Based on access token issued timestamp + expires in timestamp and current time. + * @return bool + * @since 2.2.0 */ - public function get_contacts() { - if ( ! $this->is_connected() ) { - return []; - } + private function access_token_maybe_expired() { - $contacts = get_transient( 'ctct_contact' ); + $issued_time = get_option( 'ctct_access_token_timestamp', '' ); + if ( empty( $issued_time ) ) { + // It's not expired because it doesn't exist. + // This should be filled in by now though. + return false; + } - if ( false === $contacts ) { - try { - $contacts = $this->cc()->get_contacts(); - if ( array_key_exists( 'error_key', $contacts ) && 'unauthorized' === $contacts['error_key'] ) { - $this->refresh_token(); + $current_time = time(); + $expiration_time = (int) $issued_time + (int) $this->expires_in; - $contacts = $this->cc()->get_contacts(); - } + // If we're currently above the expiration time, we're expired. + return $current_time >= $expiration_time; + } - set_transient( 'ctct_contact', $contacts, 1 * DAY_IN_SECONDS ); - return $contacts; - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); + /** + * Generate the URL an account owner would use to allow your app + * to access their account. + * After visiting the URL, the account owner is prompted to log in and allow your app to access their account. + * They are then redirected to your redirect URL with the authorization code appended as a query parameter. e.g.: + * http://localhost:8888/?code={authorization_code} + */ + public function get_authorization_url(): string { - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); + $auth_url = get_option( 'ctct_auth_url' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - } + if ( $auth_url ) { + return $auth_url; } - return $contacts; + $scopes = implode( '+', array_keys( $this->scopes ) ); + [ $code_verifier, $code_challenge ] = $this->code_challenge(); + + $state = bin2hex( random_bytes( 8 ) ); + + update_option( 'CtctConstantContactState', $state ); + + $params = [ + 'client_id' => $this->client_api_key, + 'redirect_uri' => $this->redirect_URI, + 'response_type' => 'code', + 'code_challenge' => $code_challenge, + 'code_challenge_method' => 'S256', + 'state' => $state, + 'scope' => $scopes, + ]; + + // Store generated random state and code challenge based on RFC 7636 + // https://datatracker.ietf.org/doc/html/rfc7636#section-6.1 + update_option( 'CtctConstantContactcode_verifier', $code_verifier ); + + $url = add_query_arg( $params, $this->authorize_url ); + + update_option( 'ctct_auth_url', $url ); + + return $url; } /** - * Lists of the connected CTCT account. - * + * Set our authorization headers. + * @return string[] + * @since 2.0.0 + */ + private function set_authorization(): array { + + // Set authorization header + // Make string of "API_KEY:SECRET" + $auth = $this->client_api_key; + // Base64 encode it + $credentials = base64_encode( $auth ); + // Create and set the Authorization header to use the encoded credentials + $headers = [ 'Authorization: Basic ' . $credentials, 'cache-control: no-cache' ]; + + return $headers; + } + + /** + * Make sure we don't over-do API requests, helper method to check if we're connected. + * @return boolean If connected. * @since 1.0.0 - * - * @param bool $force_skip_cache Whether or not to skip cache. - * @return array Current connect ctct lists. */ - public function get_lists( bool $force_skip_cache = false ) { + public function is_connected() { + static $token = null; - if ( ! $this->is_connected() ) { - return []; + if ( constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ) { + $token = constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ? true : false; } - $lists = get_transient( 'ctct_lists' ); + return $token; + } - if ( $force_skip_cache ) { - $lists = false; - } + /** + * Execute our API request for token acquisition. + * + * @param string $url URL to make request to. + * @param array $options Request options. + * + * @return bool + * @throws Exception + * @since 2.0.0 + */ + private function exec( $url, $options ): bool { + $response = wp_safe_remote_post( $url, $options ); - if ( false === $lists ) { + $this->last_error = ''; + $this->status_code = 0; - try { - $results = $this->cc()->get_lists(); - $lists = $results['lists'] ?? []; + if ( ! is_wp_error( $response ) ) { - if ( array_key_exists( 'error_key', $results ) && 'unauthorized' === $results['error_key'] ) { - $this->refresh_token(); + $data = json_decode( $response['body'], true ); + $json_last_error = json_last_error(); + if ( JSON_ERROR_NONE !== $json_last_error ) { + constant_contact_maybe_log_it( 'JSON Error: ', json_last_error_msg() ); + } - $results = $this->cc()->get_lists(); - $lists = $results['lists'] ?? []; + // check if the body contains error + if ( isset( $data['error'] ) ) { + if ( 'invalid_grant' === $data['error'] ) { + $this->api_errors_admin_email(); } + $this->last_error = $data['error'] . ': ' . ( $data['error_description'] ?? 'Undefined' ); + constant_contact_maybe_log_it( 'Error: ', $this->last_error ); - if ( ! empty( $lists ) ) { - set_transient( 'ctct_lists', $lists, 12 * HOUR_IN_SECONDS ); - return $lists; - } elseif ( array_key_exists( 'error_key', $results ) ) { - set_transient( 'ctct_lists', $lists, DAY_IN_SECONDS ); - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . $results['error_key'] . ': ' . $results['error_message']; - constant_contact()->get_api_utility()->log_errors($our_errors); - constant_contact_forms_maybe_set_exception_notice(); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); + return false; + } - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); + if ( ! empty( $data['access_token'] ) ) { - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_maybe_log_it( 'Refresh Token: ', 'Old Refresh Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->refresh_token ) ); + constant_contact_maybe_log_it( 'Access Token: ', 'Old Access Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->access_token ) ); + + constant_contact()->get_connect()->e_set( '_ctct_access_token', $data['access_token'] ); + constant_contact()->get_connect()->e_set( '_ctct_refresh_token', $data['refresh_token'] ); + constant_contact()->get_connect()->e_set( '_ctct_expires_in', (string) $data['expires_in'] ); + + $this->access_token = $data['access_token'] ?? ''; + $this->refresh_token = $data['refresh_token'] ?? ''; + $this->expires_in = $data['expires_in'] ?? ''; + + delete_option( 'ctct_auth_url' ); + $dateObj = current_datetime(); + $expDateObj = $dateObj->modify( '+' . $data['expires_in'] . ' seconds' ); + + constant_contact_maybe_log_it( 'Refresh Token: ', 'Refresh token successfully received' ); + constant_contact_maybe_log_it( 'Refresh Token: ', 'New Refresh Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->refresh_token ) ); + constant_contact_maybe_log_it( 'Access Token: ', 'New Access Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->access_token ) ); + constant_contact_maybe_log_it( + 'Expiration time:', + 'Current time: ' . $dateObj->format( 'Y-n-d, H:i' ) . ' Estimated expiration time: ' . $expDateObj->format( 'Y-n-d, H:i' ) + ); + + return isset( $data['access_token'], $data['refresh_token'] ); } + } else { + $this->status_code = 0; + $this->last_error = $response->get_error_message(); + constant_contact_maybe_log_it( 'Error: ', $this->last_error ); } - return $lists; + return false; } /** - * Get v2 to v3 API lists of the connected CTCT account. - * - * @since 2.0.0 + * Info of the connected CTCT account. * - * @param string $old_ids_string Comma separated list of old (v2 API) list ids. - * @param bool $force_skip_cache Whether or not to skip cache. + * @since 1.0.0 * - * @return array API v2 to v3 List ID cross references. + * @return array Current connected ctct account info. */ - public function get_v2_list_id_x_refs( string $old_ids_string, bool $force_skip_cache = false ) { + public function get_account_info() { if ( ! $this->is_connected() ) { return []; } - $list_x_refs = get_transient('ctct_list_xrefs'); + $acct_data = get_transient( 'constant_contact_acct_info' ); - if ( $force_skip_cache ) { - $list_x_refs = false; - } + /** + * Filters whether or not to bypass transient with a filter. + * + * @since 1.0.0 + * + * @param bool $value Whether or not to bypass. + */ + $bypass_acct_cache = apply_filters( 'constant_contact_bypass_acct_info_cache', false ); - if ( false === $list_x_refs ) { + if ( false === $acct_data || $bypass_acct_cache ) { try { - $list_x_refs = $this->cc()->get_updated_lists_ids( $old_ids_string ); - if ( is_array( $list_x_refs ) ) { - set_transient('ctct_list_xrefs', $list_x_refs, HOUR_IN_SECONDS ); - return $list_x_refs; + $acct_data = $this->cc()->get_account_info(); + if ( array_key_exists( 'error_key', $acct_data ) && 'unauthorized' === $acct_data['error_key'] ) { + $this->refresh_token(); + + $acct_data = $this->cc()->get_account_info(); + } + + if ( $acct_data && ! array_key_exists( 'error_key', $acct_data ) ) { + set_transient( 'constant_contact_acct_info', $acct_data, 12 * HOUR_IN_SECONDS ); } } catch ( CtctException $ex ) { add_filter( 'constant_contact_force_logging', '__return_true' ); @@ -526,40 +663,34 @@ public function get_v2_list_id_x_refs( string $old_ids_string, bool $force_skip_ } } - return $list_x_refs; + return $acct_data; } /** - * Get an individual list by ID. + * Contacts of the connected CTCT account. * * @since 1.0.0 * - * @param string $id List ID. - * @return mixed + * @return array Current connect ctct account contacts. */ - public function get_list( string $id ) { - - if ( ! esc_attr( $id ) ) { - return []; - } - + public function get_contacts() { if ( ! $this->is_connected() ) { return []; } - $list = get_transient( 'ctct_list_' . $id ); + $contacts = get_transient( 'ctct_contact' ); - if ( false === $list ) { + if ( false === $contacts ) { try { - $list = $this->cc()->get_list( $id ); - if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { + $contacts = $this->cc()->get_contacts(); + if ( array_key_exists( 'error_key', $contacts ) && 'unauthorized' === $contacts['error_key'] ) { $this->refresh_token(); - $list = $this->cc()->get_list( $id ); + $contacts = $this->cc()->get_contacts(); } - set_transient( 'ctct_lists_' . $id, $list, 1 * DAY_IN_SECONDS ); - return $list; + set_transient( 'ctct_contact', $contacts, 1 * DAY_IN_SECONDS ); + return $contacts; } catch ( CtctException $ex ) { add_filter( 'constant_contact_force_logging', '__return_true' ); $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); @@ -581,200 +712,7 @@ public function get_list( string $id ) { } } - return $list; - } - - - /** - * Add List to the connected CTCT account. - * - * @since 1.0.0 - * - * @param array $new_list API data for new list. - * @return array Current connect ctct lists. - */ - public function add_list( $new_list = [] ) { - - if ( empty( $new_list ) || ! isset( $new_list['id'] ) ) { - return []; - } - - $return_list = []; - - try { - $list = $this->cc()->get_list( esc_attr( $new_list['id'] ) ); - if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { - $this->refresh_token(); - - $list = $this->cc()->get_list( esc_attr( $new_list['id'] ) ); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); - - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - } - - if ( ! isset( $list[0]['error_key'] ) ) { - return $list; - } - - try { - - $list = new ContactList(); - - $list->name = isset( $new_list['name'] ) ? esc_attr( $new_list['name'] ) : ''; - - /** - * Filters the list status to use when adding a list. - * - * @since 1.0.0 - * - * @param string $value List status to use. - */ - $list->status = apply_filters( 'constant_contact_list_status', 'HIDDEN' ); - - $return_list = $this->cc()->add_list( (array) $list ); - if ( isset( $return_list[0]['error_message'] ) ) { - // TODO: check why it's not going to catch - throw new Exception( $return_list[0]['error_message'] ); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->xdebug_message; - - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - } - - return $return_list; - } - - /** - * Update List from the connected CTCT account. - * - * @since 1.0.0 - * - * @param array $updated_list api data for list. - * @return array current connect ctct list - */ - public function update_list( array $updated_list = [] ) { - - $return_list = false; - - try { - - $list = new ContactList(); - - $list->id = isset( $updated_list['id'] ) ? esc_attr( $updated_list['id'] ) : ''; - $list->name = isset( $updated_list['name'] ) ? esc_attr( $updated_list['name'] ) : ''; - $list->favorite = isset( $updated_list['favorite'] ) ? esc_attr( $updated_list['favorite'] ) : false; - - /** - * Filters the list status to use when updating a list. - * - * @since 1.0.0 - * - * @param string $value List status to use. - */ - $list->status = apply_filters( 'constant_contact_list_status', 'HIDDEN' ); - - $return_list = $this->cc()->update_list( (array) $list ); - if ( array_key_exists( 'error_key', $return_list ) && 'unauthorized' === $return_list['error_key'] ) { - $this->refresh_token(); - $return_list = $this->cc()->update_list( $list ); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); - - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - } - - return $return_list; - } - - /** - * Delete List from the connected CTCT account. - * - * @since 1.0.0 - * - * @param array $updated_list API data for list. - * @return mixed Current connect ctct list. - */ - public function delete_list( array $updated_list = [] ) { - - if ( ! isset( $updated_list['id'] ) ) { - return false; - } - - $list = false; - - try { - $list = $this->cc()->delete_list( $updated_list['id'] ); - if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { - $this->refresh_token(); - $list = $this->cc()->delete_list( $updated_list['id'] ); - } - } catch ( CtctException $ex ) { - add_filter( 'constant_contact_force_logging', '__return_true' ); - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $errors = $ex->getErrors(); - $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - } catch ( Exception $ex ) { - $error = new stdClass(); - $error->error_key = get_class( $ex ); - $error->error_message = $ex->getMessage(); - - add_filter( 'constant_contact_force_logging', '__return_true' ); - constant_contact_forms_maybe_set_exception_notice( $ex ); - - $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); - $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; - constant_contact()->get_api_utility()->log_errors( $our_errors ); - } - - return $list; + return $contacts; } /** @@ -783,12 +721,12 @@ public function delete_list( array $updated_list = [] ) { * request body to determine if it should create an new contact or update * an existing contact. * - * @since 1.0.0 - * @since 1.3.0 Added $form_id parameter. - * * @param array $new_contact New contact data. * @param int $form_id ID of the form being processed. + * * @return array Current connect contact. + * @since 1.3.0 Added $form_id parameter. + * @since 1.0.0 */ public function add_contact( $new_contact = [], $form_id = 0 ) { @@ -874,16 +812,15 @@ public function add_contact( $new_contact = [], $form_id = 0 ) { /** * Helper method to update contact. * - * @since 1.0.0 - * @since 1.3.0 Added $form_id parameter. - * - * @throws CtctException API exception? - * * @param string|array $list List name(s). * @param array $user_data User data. - * @param string $email email to be updated. + * @param string $email email to be updated. * @param string $form_id Form ID being processed. + * * @return mixed Response from API. + * @throws CtctException API exception? + * @since 1.0.0 + * @since 1.3.0 Added $form_id parameter. */ public function create_update_contact( $list, $email, $user_data, $form_id ) { @@ -940,29 +877,32 @@ public function create_update_contact( $list, $email, $user_data, $form_id ) { * Helper method to push as much data from a form as we can into the * Constant Contact contact thats in a list. * - * @since 1.0.0 - * @since 1.3.0 Added $form_id parameter. - * @since 1.4.5 Added $updated paramater. - * * @param object $contact Contact object. * @param array $user_data Bunch of user data. * @param string $form_id Form ID being processed. * @param bool $updated Whether or not we are updating a contact. Default false. - * @throws CtctException $error An exception error. + * * @return object Contact object, with new properties. + * @throws CtctException $error An exception error. + * @since 1.0.0 + * @since 1.3.0 Added $form_id parameter. + * @since 1.4.5 Added $updated paramater. */ public function set_contact_properties( $contact, $user_data, $form_id, $updated = false ) { if ( ! is_object( $contact ) || ! is_array( $user_data ) ) { $error = new CtctException(); - $error->setErrors( [ 'type', esc_html__( 'Not a valid contact to set properties to.', 'constant-contact-forms' ) ] ); + $error->setErrors( [ + 'type', + esc_html__( 'Not a valid contact to set properties to.', 'constant-contact-forms' ) + ] ); throw $error; } unset( $user_data['list'] ); - $address = []; - $count = 1; - $streets = []; + $address = []; + $count = 1; + $streets = []; if ( ! $updated ) { $contact->notes = []; } @@ -1013,7 +953,7 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated $address['city'] = $value; break; case 'state_address': - $address['state'] = $value; + $address['state'] = $value; if ( empty( $address['country'] ) ) { $address['country'] = 'United States'; } @@ -1077,7 +1017,7 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated ]; } - $count++; + $count ++; break; default: try { @@ -1107,461 +1047,528 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated } /** - * Make sure we don't over-do API requests, helper method to check if we're connected. + * Lists of the connected CTCT account. * * @since 1.0.0 * - * @return boolean If connected. + * @param bool $force_skip_cache Whether or not to skip cache. + * @return array Current connect ctct lists. */ - public function is_connected() { - static $token = null; + public function get_lists( bool $force_skip_cache = false ) { - if ( constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ) { - $token = constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ? true : false; + if ( ! $this->is_connected() ) { + return []; } - return $token; - } + $lists = get_transient( 'ctct_lists' ); - /** - * Helper method to output a link for our settings page tabs. - * - * @since 2022-10-24 - * @return string Settings tab URL. - */ - public function get_settings_link( $settings_tab = 'ctct_options_settings_general' ) { + if ( $force_skip_cache ) { + $lists = false; + } - return add_query_arg( - [ - 'post_type' => 'ctct_forms', - 'page' => sanitize_text_field( $settings_tab ), - ], - admin_url( 'edit.php' ) - ); + if ( false === $lists ) { + + try { + $results = $this->cc()->get_lists(); + $lists = $results['lists'] ?? []; + + if ( array_key_exists( 'error_key', $results ) && 'unauthorized' === $results['error_key'] ) { + $this->refresh_token(); + + $results = $this->cc()->get_lists(); + $lists = $results['lists'] ?? []; + } + + if ( ! empty( $lists ) ) { + set_transient( 'ctct_lists', $lists, 12 * HOUR_IN_SECONDS ); + return $lists; + } elseif ( array_key_exists( 'error_key', $results ) ) { + set_transient( 'ctct_lists', $lists, DAY_IN_SECONDS ); + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . $results['error_key'] . ': ' . $results['error_message']; + constant_contact()->get_api_utility()->log_errors($our_errors); + constant_contact_forms_maybe_set_exception_notice(); + } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); + + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + } + } + + return $lists; } /** - * Maybe get the disclosure address from the API Organization Information. + * Get v2 to v3 API lists of the connected CTCT account. * - * @since 1.0.0 + * @since 2.0.0 * - * @param bool $as_parts If true return an array. - * @return mixed + * @param string $old_ids_string Comma separated list of old (v2 API) list ids. + * @param bool $force_skip_cache Whether or not to skip cache. + * + * @return array API v2 to v3 List ID cross references. */ - public function get_disclosure_info( $as_parts = false ) { - /* - * [ - * [name] => Business Name - * [address] => 555 Business Place Ln., Beverly Hills, CA, 90210 - * ] - */ - - static $address_fields = [ 'address_line1', 'address_line2', 'address_line3', 'city', 'state_code', 'postal_code' ]; - - // Grab disclosure info from the API. - $account_info = $this->get_account_info(); + public function get_v2_list_id_x_refs( string $old_ids_string, bool $force_skip_cache = false ) { - if ( empty( $account_info ) ) { - return $as_parts ? [] : ''; + if ( ! $this->is_connected() ) { + return []; } - $disclosure = [ - 'name' => empty( $account_info->organization_name ) ? constant_contact_get_option( '_ctct_disclose_name', '' ) : $account_info->organization_name, - 'address' => constant_contact_get_option( '_ctct_disclose_address', '' ), - ]; + $list_x_refs = get_transient('ctct_list_xrefs'); - if ( empty( $disclosure['name'] ) ) { - return $as_parts ? [] : ''; + if ( $force_skip_cache ) { + $list_x_refs = false; } - // Determine the address to use for disclosure from the API. - if ( - isset( $account_info['physical_address'] ) - && count( $account_info['physical_address'] ) - ) { - $organization_address = $account_info['physical_address']; - $disclosure_address = []; + if ( false === $list_x_refs ) { - if ( is_array( $address_fields ) ) { - foreach ( $address_fields as $field ) { - if ( isset( $organization_address[ $field ] ) && strlen( $organization_address[ $field ] ) ) { - $disclosure_address[] = $organization_address[ $field ]; - } + try { + $list_x_refs = $this->cc()->get_updated_lists_ids( $old_ids_string ); + if ( is_array( $list_x_refs ) ) { + set_transient('ctct_list_xrefs', $list_x_refs, HOUR_IN_SECONDS ); + return $list_x_refs; } - } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); - $disclosure['address'] = implode( ', ', $disclosure_address ); - } elseif ( empty( $disclosure['address'] ) ) { - unset( $disclosure['address'] ); - } + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); - if ( ! empty( $account_info['website'] ) ) { - $disclosure['website'] = $account_info['website']; + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + } } - return $as_parts ? $disclosure : implode( ', ', array_values( $disclosure ) ); + return $list_x_refs; } /** - * Generate code_verifier and code_challenge for rfc7636 PKCE. - * https://datatracker.ietf.org/doc/html/rfc7636#appendix-B + * Get an individual list by ID. * - * @return array [code_verifier, code_challenge]. + * @since 1.0.0 + * + * @param string $id List ID. + * @return mixed */ - private function code_challenge( ?string $code_verifier = null ): array { - $gen = static function () { - $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; - $length = random_int( 43, 128 ); - - for ( $i = 0; $i < $length; $i++ ) { - yield $strings[ random_int( 0, 65 ) ]; - } - }; + public function get_list( string $id ) { - $code = $code_verifier ?? implode( '', iterator_to_array( $gen() ) ); + if ( ! esc_attr( $id ) ) { + return []; + } - if ( ! \preg_match( '/[A-Za-z0-9-._~]{43,128}/', $code ) ) { - return [ '', '' ]; + if ( ! $this->is_connected() ) { + return []; } - return [ $code, constant_contact()->get_api_utility()->base64url_encode( pack( 'H*', hash( 'sha256', $code ) ) ) ]; - } + $list = get_transient( 'ctct_list_' . $id ); - /** - * Handle user session details. - * - * Not used. - * - * @since 2.0.0 - * - * @param string $key - * @param string|null $value - * - * @return mixed|string - */ - public function session( string $key, ?string $value ) { - if ( $this->session_callback ) { - return call_user_func( $this->session_callback, $key, $value ); - } - if ( null === $value ) { - $value = get_user_meta( $this->this_user_id, $key, true ); - delete_user_meta( $this->this_user_id, $key, $value ); + if ( false === $list ) { + try { + $list = $this->cc()->get_list( $id ); + if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { + $this->refresh_token(); + + $list = $this->cc()->get_list( $id ); + } + + set_transient( 'ctct_lists_' . $id, $list, 1 * DAY_IN_SECONDS ); + return $list; + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); + + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); - return $value; + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + } } - update_user_meta( $this->this_user_id, $key, $value ); - - return $value; + return $list; } + /** - * Generate the URL an account owner would use to allow your app - * to access their account. + * Add List to the connected CTCT account. * - * After visiting the URL, the account owner is prompted to log in and allow your app to access their account. - * They are then redirected to your redirect URL with the authorization code appended as a query parameter. e.g.: - * http://localhost:8888/?code={authorization_code} + * @since 1.0.0 + * + * @param array $new_list API data for new list. + * @return array Current connect ctct lists. */ - public function get_authorization_url(): string { - - $auth_url = get_option( 'ctct_auth_url' ); + public function add_list( $new_list = [] ) { - if ( $auth_url ) { - return $auth_url; + if ( empty( $new_list ) || ! isset( $new_list['id'] ) ) { + return []; } - $scopes = implode( '+', array_keys( $this->scopes ) ); - [$code_verifier, $code_challenge] = $this->code_challenge(); + $return_list = []; - $state = bin2hex( random_bytes( 8 ) ); + try { + $list = $this->cc()->get_list( esc_attr( $new_list['id'] ) ); + if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { + $this->refresh_token(); - update_option( 'CtctConstantContactState', $state ); + $list = $this->cc()->get_list( esc_attr( $new_list['id'] ) ); + } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); - $params = [ - 'client_id' => $this->client_api_key, - 'redirect_uri' => $this->redirect_URI, - 'response_type' => 'code', - 'code_challenge' => $code_challenge, - 'code_challenge_method' => 'S256', - 'state' => $state, - 'scope' => $scopes, - ]; + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); - // Store generated random state and code challenge based on RFC 7636 - // https://datatracker.ietf.org/doc/html/rfc7636#section-6.1 - update_option( 'CtctConstantContactcode_verifier', $code_verifier ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + } - $url = add_query_arg( $params, $this->authorize_url ); + if ( ! isset( $list[0]['error_key'] ) ) { + return $list; + } - update_option( 'ctct_auth_url', $url ); + try { - return $url; - } + $list = new ContactList(); - /** - * Add contact to one or more lists. - * - * @author Rebekah Van Epps - * @since 1.9.0 - * @todo Update addList to use v3 - * - * @param Contact $contact Contact object. - * @param string|array $list Single list ID or array of lists. - * @return void - */ - private function add_to_list( $contact, $list ) { - if ( empty( $list ) ) { - return; - } + $list->name = isset( $new_list['name'] ) ? esc_attr( $new_list['name'] ) : ''; - $list = is_array( $list ) ? $list : [ $list ]; + /** + * Filters the list status to use when adding a list. + * + * @since 1.0.0 + * + * @param string $value List status to use. + */ + $list->status = apply_filters( 'constant_contact_list_status', 'HIDDEN' ); - foreach ( $list as $list_id ) { - $contact->list_memberships[] = esc_attr( $list_id ); + $return_list = $this->cc()->add_list( (array) $list ); + if ( isset( $return_list[0]['error_message'] ) ) { + // TODO: check why it's not going to catch + throw new Exception( $return_list[0]['error_message'] ); + } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->xdebug_message; + + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); } + + return $return_list; } /** - * Exchange an authorization code for an access token. + * Update List from the connected CTCT account. * - * Make this call by passing in the code present when the account owner is redirected back to you. - * The response will contain an 'access_token' and 'refresh_token' + * @since 1.0.0 * - * @param array of get parameters passed to redirect URL + * @param array $updated_list api data for list. + * @return array current connect ctct list */ - public function acquire_access_token(): bool { + public function update_list( array $updated_list = [] ) { - if ( ! empty( $_POST['action'] ) && 'heartbeat' === sanitize_text_field( $_POST['action'] ) ) { - return false; - } + $return_list = false; - if ( ! empty( $_POST['ctct-disconnect'] ) && 'true' === sanitize_text_field( $_POST['ctct-disconnect'] ) ) { - return false; - } - $options = get_option('ctct_options_settings'); - if ( empty( $options ) ) { - return false; - } + try { - if ( empty( $options['_ctct_form_state_authcode'] ) ) { - return false; - } + $list = new ContactList(); - $code_state = $options['_ctct_form_state_authcode']; + $list->id = isset( $updated_list['id'] ) ? esc_attr( $updated_list['id'] ) : ''; + $list->name = isset( $updated_list['name'] ) ? esc_attr( $updated_list['name'] ) : ''; + $list->favorite = isset( $updated_list['favorite'] ) ? esc_attr( $updated_list['favorite'] ) : false; - parse_str( $code_state, $parsed_code_state ); - $parsed_code_state = array_values( $parsed_code_state ); + /** + * Filters the list status to use when updating a list. + * + * @since 1.0.0 + * + * @param string $value List status to use. + */ + $list->status = apply_filters( 'constant_contact_list_status', 'HIDDEN' ); - if ( empty( $parsed_code_state[0] ) || empty( $parsed_code_state[1] ) ) { - $this->status_code = 0; - $this->last_error = 'Invalid state or auth code'; - constant_contact_maybe_log_it( 'Error: ', $this->last_error ); - return false; - } else { - $code = $parsed_code_state[0]; - $state = $parsed_code_state[1]; + $return_list = $this->cc()->update_list( (array) $list ); + if ( array_key_exists( 'error_key', $return_list ) && 'unauthorized' === $return_list['error_key'] ) { + $this->refresh_token(); + $return_list = $this->cc()->update_list( $list ); + } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); + + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); } - $expected_state = get_option( 'CtctConstantContactState' ); + return $return_list; + } - if ( ( $state ?? 'undefined' ) != $expected_state ) { - $this->status_code = 0; - $this->last_error = 'state is not correct'; - constant_contact_maybe_log_it( 'Error: ', $this->last_error ); + /** + * Delete List from the connected CTCT account. + * + * @since 1.0.0 + * + * @param array $updated_list API data for list. + * @return mixed Current connect ctct list. + */ + public function delete_list( array $updated_list = [] ) { + + if ( ! isset( $updated_list['id'] ) ) { return false; } - // Create full request URL - $body = [ - 'client_id' => $this->client_api_key, - 'code' => $code, - 'redirect_uri' => $this->redirect_URI, - 'grant_type' => 'authorization_code', - ]; - $body['code_verifier'] = get_option( 'CtctConstantContactcode_verifier' ); + $list = false; - $headers = $this->set_authorization(); + try { + $list = $this->cc()->delete_list( $updated_list['id'] ); + if ( array_key_exists( 'error_key', $list ) && 'unauthorized' === $list['error_key'] ) { + $this->refresh_token(); + $list = $this->cc()->delete_list( $updated_list['id'] ); + } + } catch ( CtctException $ex ) { + add_filter( 'constant_contact_force_logging', '__return_true' ); + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $errors = $ex->getErrors(); + $our_errors[] = $extra . ' - ' . $errors[0]->error_key . ' - ' . $errors[0]->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + constant_contact_forms_maybe_set_exception_notice( $ex ); + } catch ( Exception $ex ) { + $error = new stdClass(); + $error->error_key = get_class( $ex ); + $error->error_message = $ex->getMessage(); - $url = $this->oauth2_url; + add_filter( 'constant_contact_force_logging', '__return_true' ); + constant_contact_forms_maybe_set_exception_notice( $ex ); - $options = [ - 'body' => $body, - 'headers' => $headers, - ]; + $extra = constant_contact_location_and_line( __METHOD__, __LINE__ ); + $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message; + constant_contact()->get_api_utility()->log_errors( $our_errors ); + } - // This will be either true or false. - $result = $this->exec( $url, $options ); + return $list; + } - if ( false === $result ) { - constant_contact_set_needs_manual_reconnect( 'true' ); - } else { + /** + * Add contact to one or more lists. + * + * @param Contact $contact Contact object. + * @param string|array $list Single list ID or array of lists. + * + * @return void + * @author Rebekah Van Epps + * @since 1.9.0 + * @todo Update addList to use v3 + */ + private function add_to_list( $contact, $list ) { + if ( empty( $list ) ) { + return; + } - /** - * Fires after successful access token acquisition. - * - * @since 2.3.0 - */ - do_action( 'ctct_access_token_acquired' ); + $list = is_array( $list ) ? $list : [ $list ]; - constant_contact_set_needs_manual_reconnect( 'false' ); + foreach ( $list as $list_id ) { + $contact->list_memberships[] = esc_attr( $list_id ); } + } + /** + * Helper method to output a link for our settings page tabs. + * + * @since 2022-10-24 + * @return string Settings tab URL. + */ + public function get_settings_link( $settings_tab = 'ctct_options_settings_general' ) { - return $result; + return add_query_arg( + [ + 'post_type' => 'ctct_forms', + 'page' => sanitize_text_field( $settings_tab ), + ], + admin_url( 'edit.php' ) + ); } /** - * Refresh the access token. + * Maybe get the disclosure address from the API Organization Information. * - * @since 2.0.0 + * @since 1.0.0 * - * @return array - * @throws Exception + * @param bool $as_parts If true return an array. + * @return mixed */ - public function refresh_token() { + public function get_disclosure_info( $as_parts = false ) { + /* + * [ + * [name] => Business Name + * [address] => 555 Business Place Ln., Beverly Hills, CA, 90210 + * ] + */ - $status = []; - // Force prevent any further attempts until humans interject. - if ( constant_contact_get_needs_manual_reconnect() ) { - $status['success'] = false; - $status['reason'] = 'manual_reconnect'; + static $address_fields = [ 'address_line1', 'address_line2', 'address_line3', 'city', 'state_code', 'postal_code' ]; - return $status; - } + // Grab disclosure info from the API. + $account_info = $this->get_account_info(); - $token = constant_contact()->get_connect()->e_get( '_ctct_refresh_token' ); - if ( empty( $token ) ) { - $status['success'] = false; - $status['reason'] = 'no available token'; - return $status; + if ( empty( $account_info ) ) { + return $as_parts ? [] : ''; } - constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh token triggered' ); - - // Create full request URL - $body = [ - 'client_id' => $this->client_api_key, - 'refresh_token' => constant_contact()->get_connect()->e_get( '_ctct_refresh_token' ), - 'redirect_uri' => $this->redirect_URI, - 'grant_type' => 'refresh_token', + $disclosure = [ + 'name' => empty( $account_info->organization_name ) ? constant_contact_get_option( '_ctct_disclose_name', '' ) : $account_info->organization_name, + 'address' => constant_contact_get_option( '_ctct_disclose_address', '' ), ]; - $url = $this->oauth2_url; - $headers = $this->set_authorization(); + if ( empty( $disclosure['name'] ) ) { + return $as_parts ? [] : ''; + } - $options = [ - 'body' => $body, - 'headers' => $headers, - ]; + // Determine the address to use for disclosure from the API. + if ( + isset( $account_info['physical_address'] ) + && count( $account_info['physical_address'] ) + ) { + $organization_address = $account_info['physical_address']; + $disclosure_address = []; - $result = $this->exec( $url, $options ); + if ( is_array( $address_fields ) ) { + foreach ( $address_fields as $field ) { + if ( isset( $organization_address[ $field ] ) && strlen( $organization_address[ $field ] ) ) { + $disclosure_address[] = $organization_address[ $field ]; + } + } + } - if ( false === $result ) { - constant_contact_maybe_log_it( 'Refresh Token:', 'Expired. Refresh attempted at ' . current_datetime()->format( 'Y-n-d, H:i' ) ); - constant_contact_set_needs_manual_reconnect( 'true' ); - $status['success'] = false; - $status['reason'] = 'expired'; - } else { - delete_transient( 'ctct_lists' ); - update_option( 'ctct_access_token_timestamp', time() ); - constant_contact_set_needs_manual_reconnect( 'false' ); + $disclosure['address'] = implode( ', ', $disclosure_address ); + } elseif ( empty( $disclosure['address'] ) ) { + unset( $disclosure['address'] ); + } - $status['success'] = true; - $status['reason'] = 'refreshed'; + if ( ! empty( $account_info['website'] ) ) { + $disclosure['website'] = $account_info['website']; } - return $status; + return $as_parts ? $disclosure : implode( ', ', array_values( $disclosure ) ); } /** - * Set our authorization headers. + * Generate code_verifier and code_challenge for rfc7636 PKCE. + * https://datatracker.ietf.org/doc/html/rfc7636#appendix-B * - * @since 2.0.0 - * @return string[] + * @return array [code_verifier, code_challenge]. */ - private function set_authorization(): array { + private function code_challenge( ?string $code_verifier = null ): array { + $gen = static function () { + $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~'; + $length = random_int( 43, 128 ); - // Set authorization header - // Make string of "API_KEY:SECRET" - $auth = $this->client_api_key; - // Base64 encode it - $credentials = base64_encode( $auth ); - // Create and set the Authorization header to use the encoded credentials - $headers = [ 'Authorization: Basic ' . $credentials, 'cache-control: no-cache' ]; + for ( $i = 0; $i < $length; $i++ ) { + yield $strings[ random_int( 0, 65 ) ]; + } + }; - return $headers; + $code = $code_verifier ?? implode( '', iterator_to_array( $gen() ) ); + + if ( ! \preg_match( '/[A-Za-z0-9-._~]{43,128}/', $code ) ) { + return [ '', '' ]; + } + + return [ $code, constant_contact()->get_api_utility()->base64url_encode( pack( 'H*', hash( 'sha256', $code ) ) ) ]; } /** - * Execute our API request for token acquisition. + * Handle user session details. + * + * Not used. * * @since 2.0.0 * - * @param string $url URL to make request to. - * @param array $options Request options. + * @param string $key + * @param string|null $value * - * @return bool - * @throws Exception + * @return mixed|string */ - private function exec( $url, $options ): bool { - $response = wp_safe_remote_post( $url, $options ); - - $this->last_error = ''; - $this->status_code = 0; - - if ( ! is_wp_error( $response ) ) { - - $data = json_decode( $response['body'], true ); - $json_last_error = json_last_error(); - if ( JSON_ERROR_NONE !== $json_last_error ) { - constant_contact_maybe_log_it( 'JSON Error: ', json_last_error_msg() ); - } - - // check if the body contains error - if ( isset( $data['error'] ) ) { - if ( 'invalid_grant' === $data['error'] ) { - $this->api_errors_admin_email(); - } - $this->last_error = $data['error'] . ': ' . ( $data['error_description'] ?? 'Undefined' ); - constant_contact_maybe_log_it( 'Error: ', $this->last_error ); - return false; - } - - if ( ! empty( $data['access_token'] ) ) { - - constant_contact_maybe_log_it( 'Refresh Token: ', 'Old Refresh Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->refresh_token ) ); - constant_contact_maybe_log_it( 'Access Token: ', 'Old Access Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->access_token ) ); - - constant_contact()->get_connect()->e_set( '_ctct_access_token', $data['access_token'] ); - constant_contact()->get_connect()->e_set( '_ctct_refresh_token', $data['refresh_token'] ); - constant_contact()->get_connect()->e_set( '_ctct_expires_in', (string) $data['expires_in'] ); - - $this->access_token = $data['access_token'] ?? ''; - $this->refresh_token = $data['refresh_token'] ?? ''; - $this->expires_in = $data['expires_in'] ?? ''; - - delete_option( 'ctct_auth_url' ); - $dateObj = current_datetime(); - $expDateObj = $dateObj->modify( '+' . $data['expires_in'] . ' seconds' ); - - constant_contact_maybe_log_it( 'Refresh Token: ', 'Refresh token successfully received' ); - constant_contact_maybe_log_it( 'Refresh Token: ', 'New Refresh Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->refresh_token ) ); - constant_contact_maybe_log_it( 'Access Token: ', 'New Access Token: ' . constant_contact()->get_api_utility()->obfuscate_api_data_item( $this->access_token ) ); - constant_contact_maybe_log_it( - 'Expiration time:', - 'Current time: ' . $dateObj->format( 'Y-n-d, H:i' ) . ' Estimated expiration time: ' . $expDateObj->format( 'Y-n-d, H:i' ) - ); + public function session( string $key, ?string $value ) { + if ( $this->session_callback ) { + return call_user_func( $this->session_callback, $key, $value ); + } + if ( null === $value ) { + $value = get_user_meta( $this->this_user_id, $key, true ); + delete_user_meta( $this->this_user_id, $key, $value ); - return isset( $data['access_token'], $data['refresh_token'] ); - } - } else { - $this->status_code = 0; - $this->last_error = $response->get_error_message(); - constant_contact_maybe_log_it( 'Error: ', $this->last_error ); + return $value; } - return false; + update_user_meta( $this->this_user_id, $key, $value ); + + return $value; } /** @@ -1610,31 +1617,6 @@ private function get_note_content( $submission_data ) { return $note; } - /** - * Check if our current access token is expired. - * - * Based on access token issued timestamp + expires in timestamp and current time. - * - * @since 2.2.0 - * - * @return bool - */ - private function access_token_maybe_expired() { - - $issued_time = get_option( 'ctct_access_token_timestamp', '' ); - if ( empty( $issued_time ) ) { - // It's not expired because it doesn't exist. - // This should be filled in by now though. - return false; - } - - $current_time = time(); - $expiration_time = (int) $issued_time + (int) $this->expires_in; - - // If we're currently above the expiration time, we're expired. - return $current_time >= $expiration_time; - } - /** * Logs a missed API request to our overall log of missed requests. * From fbf64488ba24d71e91e2bb05c75df56c87daad1d Mon Sep 17 00:00:00 2001 From: Michael Beckwith Date: Mon, 13 Apr 2026 11:12:19 -0500 Subject: [PATCH 6/6] fix up changes from other merged PRs --- includes/class-api.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/includes/class-api.php b/includes/class-api.php index 43622e3e..68fddc27 100644 --- a/includes/class-api.php +++ b/includes/class-api.php @@ -551,11 +551,16 @@ private function exec( $url, $options ): bool { $this->status_code = 0; if ( ! is_wp_error( $response ) ) { - + if ( empty( $response['body'] ) ) { + constant_contact_maybe_log_it( + 'Response error: ', implode( ":", $response['response'] ) + ); + return false; + } $data = json_decode( $response['body'], true ); $json_last_error = json_last_error(); if ( JSON_ERROR_NONE !== $json_last_error ) { - constant_contact_maybe_log_it( 'JSON Error: ', json_last_error_msg() ); + constant_contact_maybe_log_it( 'JSON error: ', json_last_error_msg() ); } // check if the body contains error