' . 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' ) . '
';
}
@@ -395,7 +404,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 );
@@ -429,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 );
@@ -442,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;
@@ -471,7 +481,7 @@ public function hooks() {
*
* @since 1.0.0
*/
- public function activate() {
+ public function activate(): void {
update_option( self::$activated_date_option, time() );
}
@@ -482,7 +492,7 @@ public function activate() {
*
* @return void
*/
- public function deactivate() {
+ public function deactivate(): void {
if ( ! $this->meets_php_requirements() ) {
return;
@@ -511,7 +521,7 @@ public function deactivate() {
*
* @since 1.6.0
*/
- public function uninstall() {
+ public function uninstall(): void {
$uninstaller = new ConstantContact_Uninstall();
$uninstaller->run();
}
@@ -532,7 +542,7 @@ public function meets_php_requirements() : bool {
*
* @since 1.0.0
*/
- public function init() {
+ public function init(): void {
$this->init_debug_log();
}
@@ -543,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;
@@ -558,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' );
@@ -610,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 ),
+ };
}
/**
@@ -848,6 +853,15 @@ public function get_updates(): ConstantContact_Updates {
return $this->updates;
}
+ /**
+ * API Utility getter.
+ * @return ConstantContact_API_Utility
+ * @since 2.18.0
+ */
+ public function get_api_utility(): ConstantContact_API_Utility {
+ return $this->utility;
+ }
+
/**
* Include a file from the classes directory.
*
@@ -857,7 +871,7 @@ public function get_updates(): ConstantContact_Updates {
* @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 ) {
@@ -883,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;
}
@@ -897,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;
}
@@ -908,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 );
@@ -927,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.
@@ -939,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',
@@ -954,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;
@@ -1013,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();
}
@@ -1022,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/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..d9c7aa72
--- /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 2.18.0 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 2.18.0 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 2.18.0 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 2.18.0 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 2.18.0 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 2.18.0 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 2.18.0 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..79d27dc0 100644
--- a/includes/class-api.php
+++ b/includes/class-api.php
@@ -286,231 +286,394 @@ 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 {
- /**
- * Info of the connected CTCT account.
- *
- * @since 1.0.0
- *
- * @return array Current connected ctct account info.
- */
- public function get_account_info() {
+ if ( ! empty( $_POST['action'] ) && 'heartbeat' === sanitize_text_field( $_POST['action'] ) ) {
+ return false;
+ }
- if ( ! $this->is_connected() ) {
- return [];
+ 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;
}
- $acct_data = get_transient( 'constant_contact_acct_info' );
+ if ( empty( $options['_ctct_form_state_authcode'] ) ) {
+ return 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 );
+ $code_state = $options['_ctct_form_state_authcode'];
- if ( false === $acct_data || $bypass_acct_cache ) {
+ parse_str( $code_state, $parsed_code_state );
+ $parsed_code_state = array_values( $parsed_code_state );
- try {
- $acct_data = $this->cc()->get_account_info();
- if ( array_key_exists( 'error_key', $acct_data ) && 'unauthorized' === $acct_data['error_key'] ) {
- $this->refresh_token();
+ 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 );
- $acct_data = $this->cc()->get_account_info();
- }
+ return false;
+ } else {
+ $code = $parsed_code_state[0];
+ $state = $parsed_code_state[1];
+ }
- 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;
- $this->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();
+ $expected_state = get_option( 'CtctConstantContactState' );
- add_filter( 'constant_contact_force_logging', '__return_true' );
- constant_contact_forms_maybe_set_exception_notice( $ex );
+ 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 );
- $extra = constant_contact_location_and_line( __METHOD__, __LINE__ );
- $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message;
- $this->log_errors( $our_errors );
- }
+ return false;
}
+ // Create full request URL
+ $body = [
+ 'client_id' => $this->client_api_key,
+ 'code' => $code,
+ 'redirect_uri' => $this->redirect_URI,
+ 'grant_type' => 'authorization_code',
+ ];
- return $acct_data;
- }
+ $body['code_verifier'] = get_option( 'CtctConstantContactcode_verifier' );
- /**
- * Contacts of the connected CTCT account.
- *
- * @since 1.0.0
- *
- * @return array Current connect ctct account contacts.
- */
- public function get_contacts() {
- if ( ! $this->is_connected() ) {
- return [];
- }
+ $headers = $this->set_authorization();
- $contacts = get_transient( 'ctct_contact' );
+ $url = $this->oauth2_url;
- if ( false === $contacts ) {
- try {
- $contacts = $this->cc()->get_contacts();
- if ( array_key_exists( 'error_key', $contacts ) && 'unauthorized' === $contacts['error_key'] ) {
- $this->refresh_token();
+ $options = [
+ 'body' => $body,
+ 'headers' => $headers,
+ ];
- $contacts = $this->cc()->get_contacts();
- }
+ // This will be either true or false.
+ $result = $this->exec( $url, $options );
- 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;
- $this->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();
+ if ( false === $result ) {
+ constant_contact_set_needs_manual_reconnect( 'true' );
+ } else {
- add_filter( 'constant_contact_force_logging', '__return_true' );
- constant_contact_forms_maybe_set_exception_notice( $ex );
+ /**
+ * Fires after successful access token acquisition.
+ * @since 2.3.0
+ */
+ do_action( 'ctct_access_token_acquired' );
- $extra = constant_contact_location_and_line( __METHOD__, __LINE__ );
- $our_errors[] = $extra . ' - ' . $error->error_key . ' - ' . $error->error_message;
- $this->log_errors( $our_errors );
- }
+ constant_contact_set_needs_manual_reconnect( 'false' );
}
- return $contacts;
+
+ return $result;
}
/**
- * Lists of the connected CTCT account.
- *
- * @since 1.0.0
- *
- * @param bool $force_skip_cache Whether or not to skip cache.
- * @return array Current connect ctct lists.
+ * Refresh the access token.
+ * @return array
+ * @throws Exception
+ * @since 2.0.0
*/
- public function get_lists( bool $force_skip_cache = false ) {
+ public function refresh_token() {
- if ( ! $this->is_connected() ) {
- return [];
- }
+ $status = [];
+ $failures = (int) get_option( 'ctct_refresh_failures', 0 );
- $lists = get_transient( 'ctct_lists' );
+ // Only require manual reconnect after multiple consecutive failures.
+ // This prevents a single transient error (network blip, brief API outage,
+ // container restart) from permanently breaking the connection.
+ if ( constant_contact_get_needs_manual_reconnect() ) {
+ $status['success'] = false;
+ $status['reason'] = 'manual_reconnect';
- if ( $force_skip_cache ) {
- $lists = false;
+ return $status;
}
- if ( false === $lists ) {
+ $token = constant_contact()->get_connect()->e_get( '_ctct_refresh_token' );
+ if ( empty( $token ) ) {
+ $status['success'] = false;
+ $status['reason'] = 'no available token';
- try {
- $results = $this->cc()->get_lists();
- $lists = $results['lists'] ?? [];
+ return $status;
+ }
- if ( array_key_exists( 'error_key', $results ) && 'unauthorized' === $results['error_key'] ) {
- $this->refresh_token();
+ constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh token triggered' );
- $results = $this->cc()->get_lists();
- $lists = $results['lists'] ?? [];
- }
+ // 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',
+ ];
- 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'];
- $this->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;
- $this->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;
+ $headers = $this->set_authorization();
- 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;
- $this->log_errors( $our_errors );
+ $result = $this->exec( $url, $options );
+
+ if ( false === $result ) {
+ $failures ++;
+ update_option( 'ctct_refresh_failures', $failures );
+
+ // Distinguish between a definitive auth failure and a transient error.
+ // Only require manual reconnect for invalid_grant (revoked/expired refresh
+ // token) or after 5 consecutive failures of any kind.
+ if ( false !== strpos( $this->last_error, 'invalid_grant' ) ) {
+ constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh token revoked (invalid_grant). Manual reconnect required.' );
+ constant_contact_set_needs_manual_reconnect( 'true' );
+ $status['reason'] = 'expired';
+ } elseif ( $failures >= 5 ) {
+ constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh failed ' . $failures . ' consecutive times. Manual reconnect required.' );
+ constant_contact_set_needs_manual_reconnect( 'true' );
+ $status['reason'] = 'max_retries_exceeded';
+ } else {
+ constant_contact_maybe_log_it( 'Refresh Token:', 'Refresh failed (attempt ' . $failures . '/5). Will retry. Attempted at ' . current_datetime()->format( 'Y-n-d, H:i' ) );
+ $status['reason'] = 'transient_failure';
}
+
+ $status['success'] = false;
+ } else {
+ delete_transient( 'ctct_lists' );
+ update_option( 'ctct_access_token_timestamp', time() );
+ update_option( 'ctct_refresh_failures', 0 );
+ constant_contact_set_needs_manual_reconnect( 'false' );
+
+ $status['success'] = true;
+ $status['reason'] = 'refreshed';
}
- return $lists;
+ return $status;
}
/**
- * Get v2 to v3 API lists of the connected CTCT account.
- *
- * @since 2.0.0
- *
- * @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.
+ * 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_v2_list_id_x_refs( string $old_ids_string, bool $force_skip_cache = false ) {
-
- if ( ! $this->is_connected() ) {
- return [];
- }
+ private function access_token_maybe_expired() {
- $list_x_refs = get_transient('ctct_list_xrefs');
+ $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 ( $force_skip_cache ) {
- $list_x_refs = 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;
+ }
+
+ /**
+ * 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 {
+
+ $auth_url = get_option( 'ctct_auth_url' );
+
+ if ( $auth_url ) {
+ return $auth_url;
}
- if ( false === $list_x_refs ) {
+ $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;
+ }
+
+ /**
+ * 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
+ */
+ public function is_connected() {
+ static $token = null;
+
+ if ( constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ) {
+ $token = constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ? true : false;
+ }
+
+ return $token;
+ }
+
+ /**
+ * 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 );
+
+ $this->last_error = '';
+ $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() );
+ }
+
+ // 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' )
+ );
+
+ 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 false;
+ }
+
+ /**
+ * Info of the connected CTCT account.
+ *
+ * @since 1.0.0
+ *
+ * @return array Current connected ctct account info.
+ */
+ public function get_account_info() {
+
+ if ( ! $this->is_connected() ) {
+ return [];
+ }
+
+ $acct_data = get_transient( 'constant_contact_acct_info' );
+
+ /**
+ * 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 === $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' );
$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,50 +685,44 @@ 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 );
}
}
- 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__ );
$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,236 +734,43 @@ 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 );
}
}
- return $list;
+ return $contacts;
}
-
/**
- * Add List to the connected CTCT account.
+ * Create a new contact or update an existing contact.
+ * This method uses the email_address string value you include in the
+ * request body to determine if it should create an new contact or update
+ * an existing contact.
*
- * @since 1.0.0
+ * @param array $new_contact New contact data.
+ * @param int $form_id ID of the form being processed.
*
- * @param array $new_list API data for new list.
- * @return array Current connect ctct lists.
+ * @return array Current connect contact.
+ * @since 1.3.0 Added $form_id parameter.
+ * @since 1.0.0
*/
- public function add_list( $new_list = [] ) {
+ public function add_contact( $new_contact = [], $form_id = 0 ) {
- if ( empty( $new_list ) || ! isset( $new_list['id'] ) ) {
+ if ( ! isset( $new_contact['email'] ) ) {
return [];
}
- $return_list = [];
+ $email = sanitize_email( $new_contact['email'] );
+ // Set our list data. If we didn't get passed a list and got this far, just generate a random ID.
+ $list = $new_contact['list'] ?? 'cc_' . wp_generate_password( 15, false );
+
+ $return_contact = false;
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;
- $this->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;
- $this->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;
- $this->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;
- $this->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;
- $this->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;
- $this->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;
- $this->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;
- $this->log_errors( $our_errors );
- }
-
- return $list;
- }
-
- /**
- * Create a new contact or update an existing contact.
- * This method uses the email_address string value you include in the
- * 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.
- */
- public function add_contact( $new_contact = [], $form_id = 0 ) {
-
- if ( ! isset( $new_contact['email'] ) ) {
- return [];
- }
-
- $email = sanitize_email( $new_contact['email'] );
- // Set our list data. If we didn't get passed a list and got this far, just generate a random ID.
- $list = $new_contact['list'] ?? 'cc_' . wp_generate_password( 15, false );
-
- $return_contact = false;
-
- try {
-
- // Remove ctct-instance if present to avoid errors.
- if ( array_key_exists( 'ctct-instance', $new_contact ) ) {
- unset( $new_contact['ctct-instance'] );
+ // Remove ctct-instance if present to avoid errors.
+ if ( array_key_exists( 'ctct-instance', $new_contact ) ) {
+ unset( $new_contact['ctct-instance'] );
}
$return_contact = $this->create_update_contact( $list, $email, $new_contact, $form_id );
@@ -835,7 +799,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 +811,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,87 +826,27 @@ 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 );
}
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.
*
- * @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 ) {
@@ -965,7 +869,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();
@@ -977,7 +881,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(
@@ -999,29 +903,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 = [];
}
@@ -1072,7 +979,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';
}
@@ -1136,7 +1043,7 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated
];
}
- $count++;
+ $count ++;
break;
default:
try {
@@ -1145,7 +1052,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;
}
@@ -1166,521 +1073,528 @@ public function set_contact_properties( $contact, $user_data, $form_id, $updated
}
/**
- * Pushes all error to api_error_message.
+ * Lists of the connected CTCT account.
*
* @since 1.0.0
*
- * @throws Exception Throws Exception if encountered while attempting to log errors.
- *
- * @param array $errors Errors from API.
+ * @param bool $force_skip_cache Whether or not to skip cache.
+ * @return array Current connect ctct lists.
*/
- public function log_errors( $errors ) {
- if ( is_array( $errors ) ) {
- foreach ( $errors as $error ) {
- constant_contact_maybe_log_it(
- 'API',
- $error
- );
- }
+ public function get_lists( bool $force_skip_cache = false ) {
+
+ if ( ! $this->is_connected() ) {
+ return [];
}
- }
- /**
- * Make sure we don't over-do API requests, helper method to check if we're connected.
- *
- * @since 1.0.0
- *
- * @return boolean If connected.
- */
- public function is_connected() {
- static $token = null;
+ $lists = get_transient( 'ctct_lists' );
- if ( constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ) {
- $token = constant_contact()->get_connect()->e_get( '_ctct_access_token' ) ? true : false;
+ if ( $force_skip_cache ) {
+ $lists = false;
}
- return $token;
- }
+ if ( false === $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' ) {
+ try {
+ $results = $this->cc()->get_lists();
+ $lists = $results['lists'] ?? [];
- return add_query_arg(
- [
- 'post_type' => 'ctct_forms',
- 'page' => sanitize_text_field( $settings_tab ),
- ],
- admin_url( 'edit.php' )
- );
- }
+ if ( array_key_exists( 'error_key', $results ) && 'unauthorized' === $results['error_key'] ) {
+ $this->refresh_token();
- /**
- * 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';
+ $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 );
+ public function get_list( string $id ) {
- for ( $i = 0; $i < $length; $i++ ) {
- yield $strings[ random_int( 0, 65 ) ];
- }
- };
+ if ( ! esc_attr( $id ) ) {
+ return [];
+ }
- $code = $code_verifier ?? implode( '', iterator_to_array( $gen() ) );
+ if ( ! $this->is_connected() ) {
+ return [];
+ }
- if ( ! \preg_match( '/[A-Za-z0-9-._~]{43,128}/', $code ) ) {
- return [ '', '' ];
+ $list = get_transient( 'ctct_list_' . $id );
+
+ 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 );
+
+ $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 [ $code, $this->base64url_encode( pack( 'H*', hash( 'sha256', $code ) ) ) ];
+ return $list;
}
- /**
- * 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.
- *
- * Not used.
- *
- * @since 2.0.0
+ * Add List to the connected CTCT account.
*
- * @param string $key
- * @param string|null $value
+ * @since 1.0.0
*
- * @return mixed|string
+ * @param array $new_list API data for new list.
+ * @return array Current connect ctct lists.
*/
- 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 );
+ public function add_list( $new_list = [] ) {
- return $value;
+ if ( empty( $new_list ) || ! isset( $new_list['id'] ) ) {
+ return [];
}
- update_user_meta( $this->this_user_id, $key, $value );
+ $return_list = [];
- return $value;
- }
+ 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();
- /**
- * 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 {
+ $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();
- $auth_url = get_option( 'ctct_auth_url' );
+ add_filter( 'constant_contact_force_logging', '__return_true' );
+ constant_contact_forms_maybe_set_exception_notice( $ex );
- if ( $auth_url ) {
- return $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 );
}
- $scopes = implode( '+', array_keys( $this->scopes ) );
- [$code_verifier, $code_challenge] = $this->code_challenge();
-
- $state = bin2hex( random_bytes( 8 ) );
+ if ( ! isset( $list[0]['error_key'] ) ) {
+ return $list;
+ }
- update_option( 'CtctConstantContactState', $state );
+ try {
- $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,
- ];
+ $list = new ContactList();
- // 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 );
+ $list->name = isset( $new_list['name'] ) ? esc_attr( $new_list['name'] ) : '';
- $url = add_query_arg( $params, $this->authorize_url );
+ /**
+ * 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' );
- update_option( 'ctct_auth_url', $url );
+ $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;
- return $url;
- }
+ add_filter( 'constant_contact_force_logging', '__return_true' );
+ constant_contact_forms_maybe_set_exception_notice( $ex );
- /**
- * 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;
+ $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 );
}
- $list = is_array( $list ) ? $list : [ $list ];
-
- foreach ( $list as $list_id ) {
- $contact->list_memberships[] = esc_attr( $list_id );
- }
+ 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();
- $expected_state = get_option( 'CtctConstantContactState' );
+ add_filter( 'constant_contact_force_logging', '__return_true' );
+ constant_contact_forms_maybe_set_exception_notice( $ex );
- 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;
+ $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 );
}
- // 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;
+ return $return_list;
+ }
- $options = [
- 'body' => $body,
- 'headers' => $headers,
- ];
+ /**
+ * 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 = [] ) {
- // This will be either true or false.
- $result = $this->exec( $url, $options );
+ if ( ! isset( $updated_list['id'] ) ) {
+ return false;
+ }
- if ( false === $result ) {
- constant_contact_set_needs_manual_reconnect( 'true' );
- } else {
+ $list = false;
- /**
- * Fires after successful access token acquisition.
- *
- * @since 2.3.0
- */
- do_action( 'ctct_access_token_acquired' );
+ 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();
- constant_contact_set_needs_manual_reconnect( 'false' );
- }
+ 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 $result;
+ return $list;
}
/**
- * Refresh the access token.
+ * Add contact to one or more lists.
*
- * @since 2.0.0
+ * @param Contact $contact Contact object.
+ * @param string|array $list Single list ID or array of lists.
*
- * @return array
- * @throws Exception
+ * @return void
+ * @author Rebekah Van Epps
+ * @since 1.9.0
+ * @todo Update addList to use v3
*/
- public function refresh_token() {
-
- $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;
- }
-
- $token = constant_contact()->get_connect()->e_get( '_ctct_refresh_token' );
- if ( empty( $token ) ) {
- $status['success'] = false;
- $status['reason'] = 'no available token';
- return $status;
+ private function add_to_list( $contact, $list ) {
+ if ( empty( $list ) ) {
+ return;
}
- 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',
- ];
-
- $url = $this->oauth2_url;
- $headers = $this->set_authorization();
-
- $options = [
- 'body' => $body,
- 'headers' => $headers,
- ];
-
- $result = $this->exec( $url, $options );
-
- 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' );
+ $list = is_array( $list ) ? $list : [ $list ];
- $status['success'] = true;
- $status['reason'] = 'refreshed';
+ foreach ( $list as $list_id ) {
+ $contact->list_memberships[] = esc_attr( $list_id );
}
-
- return $status;
}
/**
- * Set our authorization headers.
+ * Helper method to output a link for our settings page tabs.
*
- * @since 2.0.0
- * @return string[]
+ * @since 2022-10-24
+ * @return string Settings tab URL.
*/
- 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' ];
+ public function get_settings_link( $settings_tab = 'ctct_options_settings_general' ) {
- return $headers;
+ return add_query_arg(
+ [
+ 'post_type' => 'ctct_forms',
+ 'page' => sanitize_text_field( $settings_tab ),
+ ],
+ admin_url( 'edit.php' )
+ );
}
/**
- * Execute our API request for token acquisition.
- *
- * @since 2.0.0
+ * Maybe get the disclosure address from the API Organization Information.
*
- * @param string $url URL to make request to.
- * @param array $options Request options.
+ * @since 1.0.0
*
- * @return bool
- * @throws Exception
+ * @param bool $as_parts If true return an array.
+ * @return mixed
*/
- private function exec( $url, $options ): bool {
- $response = wp_safe_remote_post( $url, $options );
+ public function get_disclosure_info( $as_parts = false ) {
+ /*
+ * [
+ * [name] => Business Name
+ * [address] => 555 Business Place Ln., Beverly Hills, CA, 90210
+ * ]
+ */
- $this->last_error = '';
- $this->status_code = 0;
+ static $address_fields = [ 'address_line1', 'address_line2', 'address_line3', 'city', 'state_code', 'postal_code' ];
- if ( ! is_wp_error( $response ) ) {
+ // Grab disclosure info from the API.
+ $account_info = $this->get_account_info();
- $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() );
- }
+ if ( empty( $account_info ) ) {
+ return $as_parts ? [] : '';
+ }
- // check if the body contains error
- if ( isset( $data['error'] ) ) {
- if ( 'invalid_grant' === $data['error'] ) {
- $this->api_errors_admin_email();
+ $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', '' ),
+ ];
+
+ if ( empty( $disclosure['name'] ) ) {
+ return $as_parts ? [] : '';
+ }
+
+ // 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 ( is_array( $address_fields ) ) {
+ foreach ( $address_fields as $field ) {
+ if ( isset( $organization_address[ $field ] ) && strlen( $organization_address[ $field ] ) ) {
+ $disclosure_address[] = $organization_address[ $field ];
+ }
}
- $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'] ) ) {
+ $disclosure['address'] = implode( ', ', $disclosure_address );
+ } elseif ( empty( $disclosure['address'] ) ) {
+ unset( $disclosure['address'] );
+ }
- 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 ) );
+ if ( ! empty( $account_info['website'] ) ) {
+ $disclosure['website'] = $account_info['website'];
+ }
- 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'] );
+ return $as_parts ? $disclosure : implode( ', ', array_values( $disclosure ) );
+ }
- $this->access_token = $data['access_token'] ?? '';
- $this->refresh_token = $data['refresh_token'] ?? '';
- $this->expires_in = $data['expires_in'] ?? '';
+ /**
+ * Generate code_verifier and code_challenge for rfc7636 PKCE.
+ * https://datatracker.ietf.org/doc/html/rfc7636#appendix-B
+ *
+ * @return array [code_verifier, code_challenge].
+ */
+ private function code_challenge( ?string $code_verifier = null ): array {
+ $gen = static function () {
+ $strings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
+ $length = random_int( 43, 128 );
- delete_option( 'ctct_auth_url' );
- $dateObj = current_datetime();
- $expDateObj = $dateObj->modify( '+' . $data['expires_in'] . ' seconds' );
+ for ( $i = 0; $i < $length; $i++ ) {
+ yield $strings[ random_int( 0, 65 ) ];
+ }
+ };
- 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(
- 'Expiration time:',
- 'Current time: ' . $dateObj->format( 'Y-n-d, H:i' ) . ' Estimated expiration time: ' . $expDateObj->format( 'Y-n-d, H:i' )
- );
+ $code = $code_verifier ?? implode( '', iterator_to_array( $gen() ) );
- 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 );
+ if ( ! \preg_match( '/[A-Za-z0-9-._~]{43,128}/', $code ) ) {
+ return [ '', '' ];
}
- return false;
+ return [ $code, constant_contact()->get_api_utility()->base64url_encode( pack( 'H*', hash( 'sha256', $code ) ) ) ];
}
/**
- * Obfuscate a value in our debug logs.
+ * Handle user session details.
*
- * Helps keep things private and not put into a potentially publicly accessed file.
+ * Not used.
*
- * @since 2.1.0
+ * @since 2.0.0
*
- * @param string $data_item Item to obfuscate.
+ * @param string $key
+ * @param string|null $value
*
- * @return string
+ * @return mixed|string
*/
- private function obfuscate_api_data_item( string $data_item ): string {
- $start = substr( $data_item, 0, 8 );
- return $start . '***';
+ 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 $value;
+ }
+
+ update_user_meta( $this->this_user_id, $key, $value );
+
+ return $value;
}
/**
@@ -1729,31 +1643,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.
*
@@ -1884,16 +1773,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/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..4ad06249 100644
--- a/includes/class-connect.php
+++ b/includes/class-connect.php
@@ -149,10 +149,7 @@ public function admin_page_display() {
wp_localize_script( 'ctct_form', 'ctctTexts', [ 'disconnectconfirm' => esc_html__( 'Are you sure you want to disconnect?', 'constant-contact-forms' ) ] );
wp_enqueue_script( 'ctct_form' );
- ?>
-
-
- get_api()->get_api_token() ) :
+ if ( constant_contact()->get_api()->get_api_token() ) :
$heading = esc_html__( 'Account connected!', 'constant-contact-forms' );
$description = esc_html__( 'You are connected to the Constant Contact account shown below.', 'constant-contact-forms' );
@@ -161,19 +158,20 @@ public function admin_page_display() {
$description = esc_html__( 'Issues with reauthentication for tokens occurred and a manual disconnect and reconnect is needed.', 'constant-contact-forms' );
}
?>
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
+
+
+ get_api()->get_account_info();
@@ -190,67 +188,72 @@ public function admin_page_display() {
} catch ( Exception $ex ) {
esc_html_e( 'There was an issue with retrieving connected account information. Please try again.', 'constant-contact-forms' );
}
- ?>
-