:
diff --git a/includes/class-mailchimp-admin.php b/includes/class-mailchimp-admin.php
index bf8c79a1..d49142cf 100644
--- a/includes/class-mailchimp-admin.php
+++ b/includes/class-mailchimp-admin.php
@@ -47,8 +47,8 @@ public function init() {
add_action( 'admin_menu', array( $this, 'add_admin_menu_pages' ) );
add_filter( 'admin_footer_text', array( $this, 'admin_footer_text' ) );
- $user_sync_settings = new Mailchimp_User_Sync_Settings();
- $user_sync_settings->init();
+ $user_sync = new Mailchimp_User_Sync();
+ $user_sync->init();
}
/**
diff --git a/includes/class-mailchimp-user-sync-backgroud-process.php b/includes/class-mailchimp-user-sync-backgroud-process.php
new file mode 100644
index 00000000..ad6a739c
--- /dev/null
+++ b/includes/class-mailchimp-user-sync-backgroud-process.php
@@ -0,0 +1,345 @@
+job_name, [ $this, 'run' ] );
+ }
+
+ /**
+ * Run the user sync job.
+ *
+ * @param array $item Item details to process.
+ */
+ public function run( $item = array() ) {
+ // Check if cancel request is made.
+ if ( isset( $item['job_id'] ) && get_transient( 'mailchimp_sf_cancel_user_sync_process' ) === $item['job_id'] ) {
+ delete_transient( 'mailchimp_sf_cancel_user_sync_process' );
+ return;
+ }
+
+ $list_id = $this->get_list_id();
+ $api = $this->get_api();
+
+ if ( ! $list_id || ! $api ) {
+ error_log( 'List ID or API not found' );
+ return;
+ }
+
+ error_log( 'Running user sync job' );
+ $limit = $this->get_limit();
+ $offset = $item['offset'] ? absint( $item['offset'] ) : 0;
+ $user_sync_settings = $this->get_user_sync_settings();
+ $user_roles = $user_sync_settings['user_roles'] ?? array();
+
+ if ( empty( $user_roles ) ) {
+ error_log( 'No user roles to sync' );
+ return;
+ }
+
+ $users = get_users( array(
+ 'role__in' => $user_roles,
+ 'number' => $limit,
+ 'offset' => $offset,
+ 'fields' => 'ID',
+ ) );
+
+ if ( empty( $users ) ) {
+ error_log( 'No users to sync' );
+ return;
+ }
+
+ foreach ( $users as $user ) {
+ try{
+ $this->sync_user( $user );
+ } catch ( Exception $e ) {
+ error_log( 'Error getting user: ' . $e->getMessage() );
+ continue;
+ }
+ }
+
+ $found_users = count( $users );
+ if ( $found_users < $limit ) {
+ error_log( 'No more users to sync' );
+ return;
+ }
+
+ $item['processed'] += $found_users;
+ $item['offset'] = $offset + $limit;
+ $this->schedule( array( $item ) );
+ return;
+ }
+
+ /**
+ * Sync the user.
+ *
+ * @param WP_User $user The user to sync.
+ */
+ public function sync_user( $user_id ) {
+ $list_id = $this->get_list_id();
+ $api = $this->get_api();
+ $settings = $this->get_user_sync_settings();
+ $existing_contacts_only = (bool) ($settings['existing_contacts_only'] ?? false);
+ $subscribe_status = $settings['subscriber_status'] ?? 'subscribed';
+
+ $user = get_user_by( 'id', $user_id );
+
+ if ( ! $user ) {
+ error_log( 'User not found' );
+ return;
+ }
+
+ error_log( 'Syncing user: ' . $user->ID );
+ $user_email = strtolower( trim( $user->user_email ) );
+ $user_first_name = $user->first_name;
+ $user_last_name = $user->last_name;
+
+ $merge_fields = array(
+ 'FNAME' => $user_first_name,
+ 'LNAME' => $user_last_name,
+ );
+
+ $merge_fields = apply_filters( 'mailchimp_sf_user_sync_merge_fields', $merge_fields, $user );
+
+ $current_status = $this->get_mailchimp_user_status( $user_email );
+
+ if ( $existing_contacts_only && ! $current_status ) {
+ error_log( 'User not exists on Mailchimp, skipping' );
+ return;
+ }
+
+ $request_body = array(
+ 'email_address' => $user_email,
+ 'merge_fields' => $merge_fields
+ );
+
+ if ( $current_status ) {
+ if ( $current_status === 'archived' ) {
+ $request_body['status'] = $subscribe_status;
+ } elseif ( $current_status === 'cleaned' ) {
+ $request_body['status'] = 'pending';
+ }
+ } else {
+ $request_body['status_if_new'] = $subscribe_status;
+ }
+
+ $endpoint = 'lists/' . $list_id . '/members/' . md5( $user_email ) . '?skip_merge_validation=true';
+ $response = $api->post( $endpoint, $request_body, 'PUT', $list_id );
+
+ if ( is_wp_error( $response ) ) {
+ error_log( 'Error syncing user: ' . $response->get_error_message() );
+ return;
+ }
+
+ error_log( 'User synced: ' . $user_email );
+ }
+
+ /**
+ * Schedule the user sync job.
+ *
+ * @param array $args Arguments to pass to the job.
+ */
+ public function schedule( array $args = [] ) {
+ if ( function_exists( 'as_enqueue_async_action' ) ) {
+ as_enqueue_async_action( $this->job_name, $args );
+ }
+ }
+
+ /**
+ * Unschedule the user sync job.
+ *
+ * @return bool
+ */
+ public function unschedule() {
+ if ( function_exists( 'as_unschedule_all_actions' ) ) {
+ as_unschedule_all_actions( $this->job_name );
+
+ if ( ! class_exists( 'ActionScheduler_Store' ) ) {
+ return false;
+ }
+
+ $store = ActionScheduler_Store::instance();
+
+ // Check if the job is still in progress.
+ $action_id = $store->find_action(
+ $this->job_name,
+ array(
+ 'status' => ActionScheduler_Store::STATUS_RUNNING,
+ )
+ );
+
+ // If no action running, return true.
+ if ( empty( $action_id ) ) {
+ return true;
+ }
+
+ $action = $store->fetch_action( $action_id );
+ $args = $action->get_args();
+ if ( ! empty( $args ) && isset( $args[0]['job_id'] ) ) {
+ set_transient( 'mailchimp_sf_cancel_user_sync_process', $args[0]['job_id'], 300 );
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if job is in progress.
+ *
+ * @return bool
+ */
+ public function in_progress(): bool {
+ if ( function_exists( 'as_has_scheduled_action' ) ) {
+ return as_has_scheduled_action( $this->job_name );
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the arguments for the current job.
+ *
+ * @return array|bool
+ */
+ public function get_args() {
+ if ( ! class_exists( 'ActionScheduler_Store' ) ) {
+ return false;
+ }
+
+ $store = ActionScheduler_Store::instance();
+
+ $running_action_id = $store->find_action(
+ $this->job_name,
+ array(
+ 'status' => ActionScheduler_Store::STATUS_RUNNING,
+ )
+ );
+
+ $pending_action_id = $store->find_action(
+ $this->job_name,
+ array(
+ 'status' => ActionScheduler_Store::STATUS_PENDING,
+ )
+ );
+
+ if ( empty( $running_action_id ) && empty( $pending_action_id ) ) {
+ return false;
+ }
+
+ $action_id = ! empty( $running_action_id ) ? $running_action_id : $pending_action_id;
+ $action = $store->fetch_action( $action_id );
+ $args = $action->get_args();
+
+ return $args;
+ }
+
+ /**
+ * Get the limit of users to sync.
+ *
+ * @return int
+ */
+ public function get_limit() {
+ /**
+ * Filter the limit of users to sync.
+ *
+ * @param int $limit The limit of users to sync.
+ * @return int
+ */
+ return apply_filters( 'mailchimp_sf_user_sync_limit', $this->limit );
+ }
+
+ /**
+ * Get the user sync settings.
+ *
+ * @return array
+ */
+ public function get_user_sync_settings() {
+ $user_sync = new Mailchimp_User_Sync();
+ return $user_sync->get_user_sync_settings();
+ }
+
+ /**
+ * Get the API instance.
+ *
+ * @return object
+ */
+ public function get_api() {
+ if ( ! $this->api ) {
+ $this->api = mailchimp_sf_get_api();
+ }
+
+ return $this->api;
+ }
+
+ /**
+ * Get the list ID.
+ *
+ * @return string
+ */
+ public function get_list_id() {
+ return get_option( 'mc_list_id' );
+ }
+
+ /**
+ * Get the mailchimp user status.
+ *
+ * @param string $user_email The user email.
+ * @return string
+ */
+ public function get_mailchimp_user_status( $user_email ) {
+ $list_id = $this->get_list_id();
+ $user_email = strtolower( trim( $user_email ) );
+ $api = $this->get_api();
+
+ $endpoint = 'lists/' . $list_id . '/members/' . md5( $user_email ) . '?fields=status';
+
+ $subscriber = $api->get( $endpoint, null );
+ if ( is_wp_error( $subscriber ) ) {
+ return false;
+ }
+ return $subscriber['status'];
+ }
+}
\ No newline at end of file
diff --git a/mailchimp.php b/mailchimp.php
index 6382cf04..e15e1103 100644
--- a/mailchimp.php
+++ b/mailchimp.php
@@ -94,7 +94,8 @@ function () {
require_once 'mailchimp_upgrade.php';
// Init Admin functions.
-require_once plugin_dir_path( __FILE__ ) . 'includes/admin/class-mailchimp-user-sync-settings.php';
+require_once plugin_dir_path( __FILE__ ) . 'includes/class-mailchimp-user-sync-backgroud-process.php';
+require_once plugin_dir_path( __FILE__ ) . 'includes/admin/class-mailchimp-user-sync.php';
require_once plugin_dir_path( __FILE__ ) . 'includes/class-mailchimp-admin.php';
$admin = new Mailchimp_Admin();
$admin->init();
From 267f67574fac5e3ea3de17fe138d19ff07d2f408 Mon Sep 17 00:00:00 2001
From: Dharmesh Patel
Date: Tue, 13 May 2025 19:23:54 +0530
Subject: [PATCH 04/22] Add user sync status rendering and auto refresh every
30 seconds.
---
assets/css/admin.css | 20 ++
assets/js/admin.js | 40 ++++
includes/admin/class-mailchimp-user-sync.php | 174 ++++++++++++++++++
includes/admin/templates/user-sync.php | 6 +
includes/class-mailchimp-admin.php | 1 +
...-mailchimp-user-sync-backgroud-process.php | 83 +++++++--
6 files changed, 307 insertions(+), 17 deletions(-)
diff --git a/assets/css/admin.css b/assets/css/admin.css
index c59027d5..8dbcdab4 100644
--- a/assets/css/admin.css
+++ b/assets/css/admin.css
@@ -805,3 +805,23 @@ body.toplevel_page_mailchimp_sf_options #footer-upgrade {
font-size: 0.9em;
margin-bottom: 10px;
}
+
+.mailchimp-sf-user-sync-status {
+ margin: 15px 0;
+ padding: 10px;
+ background: #fff;
+ border: 1px solid #ccd0d4;
+ border-radius: 4px;
+}
+.mailchimp-sf-user-sync-status .mailchimp-sf-sync-progress {
+ display: flex;
+ align-items: center;
+}
+.mailchimp-sf-user-sync-status .sync-status-text {
+ font-size: 14px;
+ line-height: 1.4;
+}
+
+.button.mailchimp-cancel-user-sync-button {
+ margin-left: auto;
+}
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 84c45848..10ad6854 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -436,3 +436,43 @@
}
}
})(jQuery); // eslint-disable-line no-undef
+
+// Update the user sync status.
+(function ($) {
+ const statusWrapper = $('.mailchimp-sf-user-sync-status');
+ const processRunning = statusWrapper.length;
+ if (!processRunning) {
+ return;
+ }
+
+ const params = window.mailchimp_sf_admin_params || {};
+ const ajaxUrl = params?.ajax_url;
+ const ajaxNonce = params?.user_sync_status_nonce;
+
+ const intervalId = setInterval(function () {
+ $.ajax({
+ url: ajaxUrl,
+ type: 'POST',
+ data: {
+ action: 'mailchimp_sf_get_user_sync_status',
+ nonce: ajaxNonce,
+ },
+ success(response) {
+ if (response.success && response.data) {
+ if (response.data.is_running && response.data.status) {
+ // Update the sync status on the page
+ statusWrapper.html(response.data.status);
+ } else {
+ // Clear interval and reload the page.
+ clearInterval(intervalId);
+ window.location.reload();
+ }
+ }
+ },
+ error(jqXHR, textStatus, errorThrown) {
+ // eslint-disable-next-line no-console
+ console.error('Error: ', textStatus, ', Details: ', errorThrown);
+ },
+ });
+ }, 30000); // 30000 milliseconds = 30 seconds
+})(jQuery); // eslint-disable-line no-undef
diff --git a/includes/admin/class-mailchimp-user-sync.php b/includes/admin/class-mailchimp-user-sync.php
index 25c80724..f29995a9 100644
--- a/includes/admin/class-mailchimp-user-sync.php
+++ b/includes/admin/class-mailchimp-user-sync.php
@@ -47,14 +47,26 @@ public function init() {
add_action( 'admin_init', array( $this, 'register_settings' ) );
add_action( 'admin_init', [ $this, 'setup_fields_sections' ] );
add_action( 'admin_post_mailchimp_sf_start_user_sync', [ $this, 'start_user_sync' ] );
+ add_action( 'admin_post_mailchimp_sf_cancel_user_sync', [ $this, 'cancel_user_sync' ] );
$this->background_process = new Mailchimp_User_Sync_Background_Process();
$this->background_process->init();
// Admin notices
add_action( 'admin_notices', [ $this, 'render_notices' ] );
+
+ // Ajax action handler
+ add_action( 'wp_ajax_mailchimp_sf_get_user_sync_status', [ $this, 'get_user_sync_status' ] );
+
+ // Render the user sync status.
+ add_action( 'mailchimp_sf_user_sync_status', [ $this, 'render_user_sync_status' ] );
}
+ /**
+ * Start the user sync.
+ *
+ * @since x.x.x
+ */
public function start_user_sync() {
if (
empty( $_GET['mailchimp_sf_start_user_sync_nonce'] ) ||
@@ -64,6 +76,32 @@ public function start_user_sync() {
wp_die( esc_html__( 'You don\'t have permission to perform this operation.', 'mailchimp' ) );
}
+ $return_url = add_query_arg( array( 'page' => 'mailchimp_sf_options', 'tab' => 'user_sync' ), admin_url( 'admin.php' ) );
+
+ // Check if the user is connected to Mailchimp.
+ $api = mailchimp_sf_get_api();
+ if ( ! $api ) {
+ $this->add_notice( __( 'We encountered a problem starting the user sync process due to connection issues, Please try again after reconnecting your Mailchimp account.', 'mailchimp' ), 'error' );
+ wp_safe_redirect( esc_url_raw( $return_url ) );
+ exit;
+ }
+
+ // Check if the user has selected a list.
+ $list_id = get_option( 'mc_list_id' );
+ if ( ! $list_id ) {
+ $this->add_notice( __( 'Please select a list to sync users.', 'mailchimp' ), 'error' );
+ wp_safe_redirect( esc_url_raw( $return_url ) );
+ exit;
+ }
+
+ // Check if the user sync is already running.
+ if ( $this->background_process->in_progress() ) {
+ $this->add_notice( __( 'User sync process is already running.', 'mailchimp' ), 'warning' );
+ wp_safe_redirect( esc_url_raw( $return_url ) );
+ exit;
+ }
+
+ // Job arguments.
$args = array(
array(
'job_id' => str_replace( '-', '', wp_generate_uuid4() ),
@@ -71,11 +109,37 @@ public function start_user_sync() {
'offset' => 0,
)
);
+
+ // Schedule the user sync job.
$this->background_process->schedule( $args );
// Add notice that the user sync has started.
$this->add_notice( __( 'User sync process has started.', 'mailchimp' ) );
+ // Redirect to the user sync settings page.
+ wp_safe_redirect( esc_url_raw( $return_url ) );
+ exit;
+ }
+
+ /**
+ * Cancel the user sync.
+ *
+ * @since x.x.x
+ */
+ public function cancel_user_sync() {
+ if (
+ empty( $_GET['mailchimp_sf_cancel_user_sync_nonce'] ) ||
+ ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['mailchimp_sf_cancel_user_sync_nonce'] ) ), 'mailchimp_sf_cancel_user_sync' ) ||
+ ! current_user_can( 'manage_options' )
+ ) {
+ wp_die( esc_html__( 'You don\'t have permission to perform this operation.', 'mailchimp' ) );
+ }
+
+ $unschedule = $this->background_process->unschedule();
+ if ( $unschedule ) {
+ $this->add_notice( __( 'User sync process will be cancelled soon.', 'mailchimp' ) );
+ }
+
// Redirect to the user sync settings page.
wp_safe_redirect( esc_url_raw( add_query_arg( array( 'page' => 'mailchimp_sf_options', 'tab' => 'user_sync' ), admin_url( 'admin.php' ) ) ) );
exit;
@@ -381,4 +445,114 @@ public function render_notices() {
delete_transient( $this->notices_transient_key );
}
}
+
+ /**
+ * Get the total users.
+ *
+ * @since x.x.x
+ * @return int The total users.
+ */
+ public function get_users_count() {
+ $settings = $this->get_user_sync_settings();
+ $user_roles = $settings['user_roles'] ?? array();
+ $total_users = 0;
+ $total_counts = count_users();
+ if ( ! empty( $total_counts['avail_roles'] ) && is_array( $total_counts['avail_roles'] ) ) {
+ foreach ( $total_counts['avail_roles'] as $role_name => $role_count ) {
+ if ( in_array( $role_name, $user_roles, true ) ) {
+ $total_users += $role_count;
+ }
+ }
+ }
+
+ return $total_users;
+ }
+
+ /**
+ * Get the user sync status.
+ *
+ * @since x.x.x
+ */
+ public function render_user_sync_status() {
+ $is_syncing = $this->background_process->in_progress();
+
+ if ( ! $is_syncing ) {
+ return;
+ }
+
+ ?>
+
+ render_user_sync_progress();
+ ?>
+
+ background_process->in_progress();
+
+ if ( ! $is_syncing ) {
+ return;
+ }
+
+ // Get the current progress from the background process
+ $total_users = $this->get_users_count();
+ $progress = $this->background_process->get_args();
+ $processed = $progress['processed'] ?? 0;
+ $cancel_url = wp_nonce_url(
+ add_query_arg(
+ array(
+ 'action' => 'mailchimp_sf_cancel_user_sync',
+ ),
+ admin_url('admin-post.php')
+ ),
+ 'mailchimp_sf_cancel_user_sync',
+ 'mailchimp_sf_cancel_user_sync_nonce'
+ );
+ ?>
+
+ false,
+ 'status' => '',
+ );
+
+ if ( $this->background_process->in_progress() ) {
+ $data['is_running'] = true;
+ ob_start();
+ $this->render_user_sync_progress();
+ $data['status'] = ob_get_clean();
+ }
+
+ wp_send_json_success( $data );
+ }
}
diff --git a/includes/admin/templates/user-sync.php b/includes/admin/templates/user-sync.php
index 4a37a32a..15d65364 100644
--- a/includes/admin/templates/user-sync.php
+++ b/includes/admin/templates/user-sync.php
@@ -7,6 +7,12 @@
?>
@@ -363,11 +363,34 @@ public function subscriber_status_field() {
-
+
-
+ get_users_count();
+ echo wp_kses(
+ sprintf(
+ _n(
+ 'You will need %1$sa Mailchimp plan%2$s that includes %3$d contact.',
+ 'You will need %1$sa Mailchimp plan%2$s that includes %3$d contacts.',
+ absint( $users_count )
+ ),
+ '',
+ '',
+ absint( $users_count ),
+ ),
+ array(
+ 'a' => array(
+ 'href' => array(),
+ 'target' => array(),
+ 'rel' => array(),
+ ),
+ )
+ )
+ ?>
+
+
Date: Wed, 14 May 2025 17:23:18 +0530
Subject: [PATCH 06/22] Display Sync errors and better sync status.
---
assets/css/admin.css | 11 ++
assets/js/admin.js | 92 +++++++++-
includes/admin/class-mailchimp-user-sync.php | 149 +++++++++++++++-
includes/admin/templates/user-sync.php | 9 +-
includes/class-mailchimp-admin.php | 26 +--
...-mailchimp-user-sync-backgroud-process.php | 164 +++++++++++++-----
lib/mailchimp/mailchimp.php | 15 +-
7 files changed, 401 insertions(+), 65 deletions(-)
diff --git a/assets/css/admin.css b/assets/css/admin.css
index 8dbcdab4..7e725f89 100644
--- a/assets/css/admin.css
+++ b/assets/css/admin.css
@@ -825,3 +825,14 @@ body.toplevel_page_mailchimp_sf_options #footer-upgrade {
.button.mailchimp-cancel-user-sync-button {
margin-left: auto;
}
+
+.mailchimp-sf-user-sync-errors {
+ margin-top: 2rem;
+}
+
+.mailchimp-sf-user-sync-errors-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
diff --git a/assets/js/admin.js b/assets/js/admin.js
index 10ad6854..9898acdc 100644
--- a/assets/js/admin.js
+++ b/assets/js/admin.js
@@ -446,8 +446,8 @@
}
const params = window.mailchimp_sf_admin_params || {};
- const ajaxUrl = params?.ajax_url;
- const ajaxNonce = params?.user_sync_status_nonce;
+ const ajaxUrl = params.ajax_url;
+ const ajaxNonce = params.user_sync_status_nonce;
const intervalId = setInterval(function () {
$.ajax({
@@ -476,3 +476,91 @@
});
}, 30000); // 30000 milliseconds = 30 seconds
})(jQuery); // eslint-disable-line no-undef
+
+// User Sync Error logs.
+(function ($) {
+ const userSyncErrors = $('.mailchimp-sf-user-sync-errors');
+ if (!userSyncErrors) {
+ return;
+ }
+
+ const params = window.mailchimp_sf_admin_params || {};
+ const tableSelector = 'table.mailchimp-sf-user-sync-errors-table';
+ const noErrorsFoundRow =
+ '| ' + params.no_errors_found + ' | ';
+ $('#mailchimp-sf-clear-user-sync-errors').on('click', function (e) {
+ e.preventDefault();
+
+ $(tableSelector).block({
+ message: null,
+ overlayCSS: {
+ opacity: 0.2,
+ },
+ });
+
+ $.ajax({
+ url: params.ajax_url,
+ type: 'POST',
+ data: {
+ action: 'mailchimp_sf_delete_user_sync_error',
+ id: 'all',
+ nonce: params.delete_user_sync_error_nonce,
+ },
+ success(response) {
+ if (response && response.success) {
+ $(tableSelector + ' tbody').html(noErrorsFoundRow);
+ $('#mailchimp-sf-clear-user-sync-errors').prop('disabled', true);
+ } else {
+ window.location.reload();
+ }
+ $(tableSelector).unblock();
+ },
+ error(jqXHR, textStatus, errorThrown) {
+ // eslint-disable-next-line no-console
+ console.error('Error: ', textStatus, ', Details: ', errorThrown);
+ window.location.reload();
+ },
+ });
+ });
+
+ $(tableSelector).on('click', '.mailchimp-sf-user-sync-error-delete', function (e) {
+ e.preventDefault();
+
+ $(tableSelector).block({
+ message: null,
+ overlayCSS: {
+ opacity: 0.2,
+ },
+ });
+
+ const errorId = $(this).data('id');
+ $.ajax({
+ url: params.ajax_url,
+ type: 'POST',
+ data: {
+ action: 'mailchimp_sf_delete_user_sync_error',
+ nonce: params.delete_user_sync_error_nonce,
+ id: errorId,
+ },
+ success(response) {
+ if (response && response.success) {
+ const rowId = '#row-' + errorId;
+ $(rowId).remove();
+
+ if (!$(tableSelector + ' tbody tr').length) {
+ $(tableSelector + ' tbody').html(noErrorsFoundRow);
+ $('#mailchimp-sf-clear-user-sync-errors').prop('disabled', true);
+ }
+ } else {
+ window.location.reload();
+ }
+ $(tableSelector).unblock();
+ },
+ error(jqXHR, textStatus, errorThrown) {
+ // eslint-disable-next-line no-console
+ console.error('Error: ', textStatus, ', Details: ', errorThrown);
+ window.location.reload();
+ },
+ });
+ });
+})(jQuery); // eslint-disable-line no-undef
diff --git a/includes/admin/class-mailchimp-user-sync.php b/includes/admin/class-mailchimp-user-sync.php
index 83e44d57..dd01fa1c 100644
--- a/includes/admin/class-mailchimp-user-sync.php
+++ b/includes/admin/class-mailchimp-user-sync.php
@@ -25,6 +25,14 @@ class Mailchimp_User_Sync {
*/
protected $option_name = 'mailchimp_sf_user_sync_settings';
+ /**
+ * The errors option name.
+ *
+ * @since x.x.x
+ * @var string
+ */
+ protected $errors_option_name = 'mailchimp_sf_user_sync_errors';
+
/**
* The background process.
*
@@ -57,9 +65,10 @@ public function init() {
// Ajax action handler
add_action( 'wp_ajax_mailchimp_sf_get_user_sync_status', [ $this, 'get_user_sync_status' ] );
-
- // Render the user sync status.
- add_action( 'mailchimp_sf_user_sync_status', [ $this, 'render_user_sync_status' ] );
+ add_action( 'wp_ajax_mailchimp_sf_delete_user_sync_error', [ $this, 'delete_user_sync_error' ] );
+ // Render the user sync status and errors.
+ add_action( 'mailchimp_sf_user_sync_before_form', [ $this, 'render_user_sync_status' ] );
+ add_action( 'mailchimp_sf_user_sync_after_form', [ $this, 'render_user_sync_errors' ] );
}
/**
@@ -106,6 +115,9 @@ public function start_user_sync() {
array(
'job_id' => str_replace( '-', '', wp_generate_uuid4() ),
'processed' => 0,
+ 'failed' => 0,
+ 'success' => 0,
+ 'skipped' => 0,
'offset' => 0,
)
);
@@ -406,7 +418,7 @@ public function existing_contacts_only_field() {
?>
/>
-
+
get_users_count();
$progress = $this->background_process->get_args();
+ $progress = current( $progress ) ?? array();
$processed = $progress['processed'] ?? 0;
+ $success = $progress['success'] ?? 0;
+ $failed = $progress['failed'] ?? 0;
+ $skipped = $progress['skipped'] ?? 0;
$cancel_url = wp_nonce_url(
add_query_arg(
array(
@@ -544,9 +560,12 @@ public function render_user_sync_progress() {
@@ -578,4 +597,122 @@ public function get_user_sync_status() {
wp_send_json_success( $data );
}
+
+ /**
+ * Get the user sync errors.
+ *
+ * @since x.x.x
+ * @return array The user sync errors.
+ */
+ public function get_user_sync_errors() {
+ return get_option( $this->errors_option_name, array() );
+ }
+
+ /**
+ * Set the user sync errors.
+ *
+ * @since x.x.x
+ * @param array $errors The user sync errors.
+ */
+ public function set_user_sync_errors( $errors ) {
+ if ( ! is_array( $errors ) || empty( $errors ) ) {
+ return;
+ }
+
+ $current_errors = $this->get_user_sync_errors();
+ $errors = array_merge( $current_errors, $errors );
+ update_option( $this->errors_option_name, $errors );
+ }
+
+ /**
+ * Delete the user sync error.
+ *
+ * @since x.x.x
+ */
+ public function delete_user_sync_errors( $id ) {
+ if ( 'all' === $id ) {
+ delete_option( $this->errors_option_name );
+ return;
+ }
+
+ $errors = $this->get_user_sync_errors();
+ if ( ! isset( $errors[ $id ] ) ) {
+ return;
+ }
+
+ unset( $errors[ $id ] );
+ update_option( $this->errors_option_name, $errors );
+ }
+
+ /**
+ * Render the user sync errors.
+ *
+ * @since x.x.x
+ */
+ public function render_user_sync_errors() {
+ $errors = $this->get_user_sync_errors();
+
+ if ( empty( $errors ) ) {
+ return;
+ }
+
+ // Get last 100 records
+ $errors = array_slice( $errors, -100 );
+
+ ?>
+
+
+
+
+
+ |
+ |
+ |
+
+
+
+ $error ) {
+ ?>
+
+ |
+ |
+
+
+ |
+
+
+
+
+
+ |
+ |
+ |
+
+
+
+
+ delete_user_sync_errors( $id );
+
+ // Send the success response
+ wp_send_json_success();
+ }
}
diff --git a/includes/admin/templates/user-sync.php b/includes/admin/templates/user-sync.php
index 15d65364..a00e536a 100644
--- a/includes/admin/templates/user-sync.php
+++ b/includes/admin/templates/user-sync.php
@@ -11,7 +11,7 @@
/**
* Render the user sync status.
*/
- do_action( 'mailchimp_sf_user_sync_status' );
+ do_action( 'mailchimp_sf_user_sync_before_form' );
?>
+
+
diff --git a/includes/class-mailchimp-admin.php b/includes/class-mailchimp-admin.php
index 59515df6..fbb1d25f 100644
--- a/includes/class-mailchimp-admin.php
+++ b/includes/class-mailchimp-admin.php
@@ -476,20 +476,22 @@ public function enqueue_admin_page_scripts( $hook_suffix ) {
wp_enqueue_style( 'mailchimp_sf_admin_css', MCSF_URL . 'assets/css/admin.css', array( 'wp-jquery-ui-dialog' ), true );
wp_enqueue_script( 'showMe', MCSF_URL . 'assets/js/hidecss.js', array( 'jquery' ), MCSF_VER, true );
- wp_enqueue_script( 'mailchimp_sf_admin', MCSF_URL . 'assets/js/admin.js', array( 'jquery', 'jquery-ui-dialog' ), MCSF_VER, true );
+ wp_enqueue_script( 'mailchimp_sf_admin', MCSF_URL . 'assets/js/admin.js', array( 'jquery', 'jquery-ui-dialog', 'jquery-blockui' ), MCSF_VER, true );
$data = array(
- 'ajax_url' => esc_url( admin_url( 'admin-ajax.php' ) ),
- 'oauth_url' => esc_url( $this->oauth_url ),
- 'oauth_start_nonce' => wp_create_nonce( 'mailchimp_sf_oauth_start_nonce' ),
- 'oauth_finish_nonce' => wp_create_nonce( 'mailchimp_sf_oauth_finish_nonce' ),
- 'oauth_window_name' => esc_html__( 'Mailchimp For WordPress OAuth', 'mailchimp' ),
- 'generic_error' => esc_html__( 'An error occurred. Please try again.', 'mailchimp' ),
- 'modal_title' => esc_html__( 'Login Popup is blocked!', 'mailchimp' ),
- 'modal_button_try_again' => esc_html__( 'Try again', 'mailchimp' ),
- 'modal_button_cancel' => esc_html__( 'No, cancel!', 'mailchimp' ),
- 'admin_settings_url' => esc_url( admin_url( 'admin.php?page=mailchimp_sf_options' ) ),
- 'user_sync_status_nonce' => wp_create_nonce( 'mailchimp_sf_user_sync_status' ),
+ 'ajax_url' => esc_url( admin_url( 'admin-ajax.php' ) ),
+ 'oauth_url' => esc_url( $this->oauth_url ),
+ 'oauth_start_nonce' => wp_create_nonce( 'mailchimp_sf_oauth_start_nonce' ),
+ 'oauth_finish_nonce' => wp_create_nonce( 'mailchimp_sf_oauth_finish_nonce' ),
+ 'oauth_window_name' => esc_html__( 'Mailchimp For WordPress OAuth', 'mailchimp' ),
+ 'generic_error' => esc_html__( 'An error occurred. Please try again.', 'mailchimp' ),
+ 'modal_title' => esc_html__( 'Login Popup is blocked!', 'mailchimp' ),
+ 'modal_button_try_again' => esc_html__( 'Try again', 'mailchimp' ),
+ 'modal_button_cancel' => esc_html__( 'No, cancel!', 'mailchimp' ),
+ 'admin_settings_url' => esc_url( admin_url( 'admin.php?page=mailchimp_sf_options' ) ),
+ 'user_sync_status_nonce' => wp_create_nonce( 'mailchimp_sf_user_sync_status' ),
+ 'delete_user_sync_error_nonce' => wp_create_nonce( 'mailchimp_sf_delete_user_sync_error' ),
+ 'no_errors_found' => esc_html__( 'No errors found', 'mailchimp' ),
);
// Create account page specific data.
diff --git a/includes/class-mailchimp-user-sync-backgroud-process.php b/includes/class-mailchimp-user-sync-backgroud-process.php
index 83da3fea..0edfd94f 100644
--- a/includes/class-mailchimp-user-sync-backgroud-process.php
+++ b/includes/class-mailchimp-user-sync-backgroud-process.php
@@ -87,6 +87,7 @@ public function run( $item = array() ) {
$offset = $item['offset'] ? absint( $item['offset'] ) : 0;
$user_sync_settings = $this->get_user_sync_settings();
$user_roles = $user_sync_settings['user_roles'] ?? array();
+ $errors = array();
// If no user roles to sync, add a notice and return.
if ( empty( $user_roles ) ) {
@@ -121,23 +122,55 @@ public function run( $item = array() ) {
}
// Sync users.
- foreach ( $users as $user ) {
+ foreach ( $users as $user_id ) {
try{
- $this->sync_user( $user );
+ $user = get_user_by( 'id', $user_id );
+ if ( ! $user ) {
+ $this->log( 'User not found' );
+ $item['skipped'] += 1;
+ continue;
+ }
+
+ $synced = $this->sync_user( $user );
+ if ( is_wp_error( $synced ) ) {
+ $item['failed'] += 1;
+ $errors[ uniqid('mailchimp_sf_error_') ] = array(
+ 'time' => time(),
+ 'email' => $user->user_email,
+ 'error' => $synced->get_error_message(),
+ );
+ } elseif ( $synced ) {
+ $item['success'] += 1;
+ } else {
+ $item['skipped'] += 1;
+ }
} catch ( Exception $e ) {
$this->log( 'Error getting user: ' . $e->getMessage() );
+ $item['failed'] += 1;
+ $errors[ uniqid('mailchimp_sf_error_') ] = array(
+ 'time' => time(),
+ 'email' => $user->user_email,
+ 'error' => $e->getMessage(),
+ );
continue;
}
}
+ // Save errors.
+ $this->user_sync->set_user_sync_errors( $errors );
+
// If no more users to sync, add a notice and return.
$found_users = count( $users );
if ( $found_users < $limit ) {
- $this->log( 'No more users to sync, User sync process completed.' );
+ $processed += $found_users;
+ $this->log( 'No more users to sync, User sync process completed. ' . absint( $processed ) . ' users processed.' );
$this->user_sync->add_notice(
sprintf(
- _n( 'User sync process completed. %d user processed.', 'User sync process completed. %d users processed.', $processed, 'mailchimp' ),
- $processed
+ _n( 'User sync process completed. %d user processed (Synced: %d, Failed: %d, Skipped: %d).', 'User sync process completed. %d users processed (Synced: %d, Failed: %d, Skipped: %d).', absint( $processed ), 'mailchimp' ),
+ absint( $processed ),
+ absint( $item['success'] ),
+ absint( $item['failed'] ),
+ absint( $item['skipped'] )
),
'success'
);
@@ -154,65 +187,112 @@ public function run( $item = array() ) {
/**
* Sync the user.
*
- * @param WP_User $user The user to sync.
+ * @param WP_User $user The user.
+ * @return bool|WP_Error True if the user is synced, WP_Error if there is an error and false if the user is not found or not synced.
*/
- public function sync_user( $user_id ) {
- $list_id = $this->get_list_id();
- $api = $this->get_api();
- $settings = $this->get_user_sync_settings();
+ public function sync_user( $user ) {
+ $list_id = $this->get_list_id();
+ $api = $this->get_api();
+ $settings = $this->get_user_sync_settings();
$existing_contacts_only = (bool) ($settings['existing_contacts_only'] ?? false);
- $subscribe_status = $settings['subscriber_status'] ?? 'subscribed';
-
- $user = get_user_by( 'id', $user_id );
+ $subscribe_status = $settings['subscriber_status'] ?? 'pending';
- if ( ! $user ) {
- $this->log( 'User not found' );
- return;
- }
-
- $this->log( 'Syncing user: ' . $user->ID );
+ $this->log( 'Syncing user: ' . $user->user_email . ' (ID: ' . $user->ID . ')' );
$user_email = strtolower( trim( $user->user_email ) );
- $user_first_name = $user->first_name;
- $user_last_name = $user->last_name;
-
- $merge_fields = array(
- 'FNAME' => $user_first_name,
- 'LNAME' => $user_last_name,
- );
-
- $merge_fields = apply_filters( 'mailchimp_sf_user_sync_merge_fields', $merge_fields, $user );
+ // Check if user exists on Mailchimp.
$current_status = $this->get_mailchimp_user_status( $user_email );
if ( $existing_contacts_only && ! $current_status ) {
$this->log( 'User not exists on Mailchimp, skipping' );
- return;
+ return false;
}
$request_body = array(
'email_address' => $user_email,
- 'merge_fields' => $merge_fields
+ 'merge_fields' => $this->get_user_merge_fields( $user ),
+ 'status' => $this->get_subscribe_status( $subscribe_status, $current_status, $user )
);
+ $endpoint = 'lists/' . $list_id . '/members/' . md5( $user_email ) . '?skip_merge_validation=true';
+ $response = $api->post( $endpoint, $request_body, 'PUT', $list_id, true );
+
+ if ( is_wp_error( $response ) ) {
+ $this->log( 'Error syncing user: ' . $response->get_error_message() );
+ return $response;
+ }
+
+ $this->log( 'User synced: ' . $user_email . ' Response: ' . wp_json_encode( $response ) );
+ return true;
+ }
+
+ /**
+ * Get the subscribe status.
+ *
+ * @param string $subscribe_status The subscribe status.
+ * @param string $current_status The current status.
+ * @param WP_User $user The user.
+ * @return string
+ */
+ public function get_subscribe_status( $subscribe_status, $current_status, $user ) {
if ( $current_status ) {
- if ( $current_status === 'archived' ) {
- $request_body['status'] = $subscribe_status;
- } elseif ( $current_status === 'cleaned' ) {
- $request_body['status'] = 'pending';
+ switch ( $current_status ) {
+ // If user is already subscribed, unsubscribed or transactional, don't change the status.
+ case 'subscribed':
+ case 'unsubscribed':
+ case 'transactional':
+ $subscribe_status = $current_status;
+ break;
+
+ // If user is cleaned, set the status as pending.
+ case 'cleaned':
+ $subscribe_status = 'pending';
+ break;
+
+ // If user is archived, pending or anything else, set the status as per the subscribe status in settings.
+ case 'archived':
+ case 'pending':
+ default:
+ break;
}
- } else {
- $request_body['status_if_new'] = $subscribe_status;
}
- $endpoint = 'lists/' . $list_id . '/members/' . md5( $user_email ) . '?skip_merge_validation=true';
- $response = $api->post( $endpoint, $request_body, 'PUT', $list_id );
+ /**
+ * Filter the subscribe status.
+ *
+ * @param string $subscribe_status The subscribe status set in settings.
+ * @param string $current_status The current subscribe status of the user on Mailchimp.
+ * @param WP_User $user The user.
+ * @return string
+ */
+ return apply_filters( 'mailchimp_sf_user_sync_subscribe_status', $subscribe_status, $current_status, $user );
+ }
- if ( is_wp_error( $response ) ) {
- $this->log( 'Error syncing user: ' . $response->get_error_message() );
- return;
+ /**
+ * Get the user merge fields.
+ *
+ * @param WP_User $user The user to get the merge fields for.
+ * @return array
+ */
+ public function get_user_merge_fields( $user ) {
+ $merge_fields = array();
+
+ if ( !empty( $user->first_name ) ) {
+ $merge_fields['FNAME'] = $user->first_name;
}
- $this->log( 'User synced: ' . $user_email );
+ if ( !empty( $user->last_name ) ) {
+ $merge_fields['LNAME'] = $user->last_name;
+ }
+
+ /**
+ * Filter the user merge fields.
+ *
+ * @param array $merge_fields The merge fields.
+ * @param WP_User $user The user.
+ * @return array
+ */
+ return apply_filters( 'mailchimp_sf_user_sync_merge_fields', $merge_fields, $user );
}
/**
diff --git a/lib/mailchimp/mailchimp.php b/lib/mailchimp/mailchimp.php
index 43214163..77930a9a 100644
--- a/lib/mailchimp/mailchimp.php
+++ b/lib/mailchimp/mailchimp.php
@@ -146,9 +146,10 @@ public function get( $endpoint, $count = 10, $fields = array() ) {
* @param string $body The body of the request
* @param string $method The request method.
* @param string $list_id The list id.
+ * @param boolean $is_sync Whether the request is for user sync.
* @return mixed
*/
- public function post( $endpoint, $body, $method = 'POST', $list_id = '' ) {
+ public function post( $endpoint, $body, $method = 'POST', $list_id = '', $is_sync = false ) {
$url = $this->api_url . $endpoint;
$headers = array();
@@ -183,7 +184,17 @@ public function post( $endpoint, $body, $method = 'POST', $list_id = '' ) {
update_option( 'mailchimp_sf_auth_error', true );
}
- $body = json_decode( $request['body'], true );
+ $body = json_decode( $request['body'], true );
+
+ // If the request is for user sync, return the error message from the API.
+ if ( $is_sync ) {
+ if ( isset( $body['detail'] ) && ! empty( $body['detail'] ) ) {
+ return new WP_Error( 'mc-subscribe-error-api', $body['detail'] );
+ } else {
+ return new WP_Error( 'mc-subscribe-error-api', $request['body'] );
+ }
+ }
+
$merges = get_option( 'mc_merge_vars' );
// Get merge fields for the list if we have a list id.
if ( ! empty( $list_id ) ) {
From 80aa4d181dbbfb18459713baf0887befa66d8e03 Mon Sep 17 00:00:00 2001
From: Dharmesh Patel
Date: Wed, 14 May 2025 17:34:06 +0530
Subject: [PATCH 07/22] Some code improvements.
---
includes/admin/class-mailchimp-user-sync.php | 80 ++++++++++++-------
includes/admin/templates/settings.php | 6 +-
includes/admin/templates/user-sync.php | 2 +-
...-mailchimp-user-sync-backgroud-process.php | 52 ++++++------
4 files changed, 84 insertions(+), 56 deletions(-)
diff --git a/includes/admin/class-mailchimp-user-sync.php b/includes/admin/class-mailchimp-user-sync.php
index dd01fa1c..36c7feb1 100644
--- a/includes/admin/class-mailchimp-user-sync.php
+++ b/includes/admin/class-mailchimp-user-sync.php
@@ -85,7 +85,13 @@ public function start_user_sync() {
wp_die( esc_html__( 'You don\'t have permission to perform this operation.', 'mailchimp' ) );
}
- $return_url = add_query_arg( array( 'page' => 'mailchimp_sf_options', 'tab' => 'user_sync' ), admin_url( 'admin.php' ) );
+ $return_url = add_query_arg(
+ array(
+ 'page' => 'mailchimp_sf_options',
+ 'tab' => 'user_sync',
+ ),
+ admin_url( 'admin.php' )
+ );
// Check if the user is connected to Mailchimp.
$api = mailchimp_sf_get_api();
@@ -119,7 +125,7 @@ public function start_user_sync() {
'success' => 0,
'skipped' => 0,
'offset' => 0,
- )
+ ),
);
// Schedule the user sync job.
@@ -153,7 +159,17 @@ public function cancel_user_sync() {
}
// Redirect to the user sync settings page.
- wp_safe_redirect( esc_url_raw( add_query_arg( array( 'page' => 'mailchimp_sf_options', 'tab' => 'user_sync' ), admin_url( 'admin.php' ) ) ) );
+ wp_safe_redirect(
+ esc_url_raw(
+ add_query_arg(
+ array(
+ 'page' => 'mailchimp_sf_options',
+ 'tab' => 'user_sync',
+ ),
+ admin_url( 'admin.php' )
+ )
+ )
+ );
exit;
}
@@ -249,16 +265,17 @@ public function setup_fields_sections() {
* Get the user sync settings.
*
* @since x.x.x
- * @return array The user sync settings.
+ * @param string|null $key The key to get.
+ * @return array|null The user sync settings.
*/
public function get_user_sync_settings( $key = null ) {
$default_settings = array(
- 'enable_user_sync' => 0,
- 'user_roles' => array(
- 'subscriber' => 'subscriber'
+ 'enable_user_sync' => 0,
+ 'user_roles' => array(
+ 'subscriber' => 'subscriber',
),
'existing_contacts_only' => 0,
- 'subscriber_status' => 'pending'
+ 'subscriber_status' => 'pending',
);
$settings = get_option( $this->option_name, array() );
@@ -275,7 +292,7 @@ public function get_user_sync_settings( $key = null ) {
* Sanitize the user sync settings.
*
* @since x.x.x
- * @param array $settings The settings to sanitize.
+ * @param array $new_settings The settings to sanitize.
* @return array The sanitized settings.
*/
public function sanitize_user_sync_settings( $new_settings ) {
@@ -326,7 +343,7 @@ public function user_roles_field() {
*
* @since x.x.x
*/
- public function enable_user_sync_field( $args ) {
+ public function enable_user_sync_field() {
$value = $this->get_user_sync_settings( 'enable_user_sync' );
?>
get_users_count();
echo wp_kses(
sprintf(
+ /* translators: %1$s: opening anchor tag, %2$s: closing anchor tag, %3$d: number of contacts. */
_n(
'You will need %1$sa Mailchimp plan%2$s that includes %3$d contact.',
'You will need %1$sa Mailchimp plan%2$s that includes %3$d contacts.',
@@ -394,9 +412,9 @@ public function subscriber_status_field() {
),
array(
'a' => array(
- 'href' => array(),
+ 'href' => array(),
'target' => array(),
- 'rel' => array(),
+ 'rel' => array(),
),
)
)
@@ -413,7 +431,7 @@ public function subscriber_status_field() {
* @since x.x.x
*/
public function existing_contacts_only_field() {
- $settings = $this->get_user_sync_settings();
+ $settings = $this->get_user_sync_settings();
$existing_contacts_only = isset( $settings['existing_contacts_only'] ) ? $settings['existing_contacts_only'] : 0;
?>
/>
@@ -488,9 +506,9 @@ public function render_notices() {
* @return int The total users.
*/
public function get_users_count() {
- $settings = $this->get_user_sync_settings();
- $user_roles = $settings['user_roles'] ?? array();
- $total_users = 0;
+ $settings = $this->get_user_sync_settings();
+ $user_roles = $settings['user_roles'] ?? array();
+ $total_users = 0;
$total_counts = count_users();
if ( ! empty( $total_counts['avail_roles'] ) && is_array( $total_counts['avail_roles'] ) ) {
foreach ( $total_counts['avail_roles'] as $role_name => $role_count ) {
@@ -509,7 +527,7 @@ public function get_users_count() {
* @since x.x.x
*/
public function render_user_sync_status() {
- $is_syncing = $this->background_process->in_progress();
+ $is_syncing = $this->background_process->in_progress();
if ( ! $is_syncing ) {
return;
@@ -530,7 +548,7 @@ public function render_user_sync_status() {
* @since x.x.x
*/
public function render_user_sync_progress() {
- $is_syncing = $this->background_process->in_progress();
+ $is_syncing = $this->background_process->in_progress();
if ( ! $is_syncing ) {
return;
@@ -549,7 +567,7 @@ public function render_user_sync_progress() {
array(
'action' => 'mailchimp_sf_cancel_user_sync',
),
- admin_url('admin-post.php')
+ admin_url( 'admin-post.php' )
),
'mailchimp_sf_cancel_user_sync',
'mailchimp_sf_cancel_user_sync_nonce'
@@ -560,17 +578,18 @@ public function render_user_sync_progress() {
-
+
false,
'status' => '',
);
@@ -620,7 +639,7 @@ public function set_user_sync_errors( $errors ) {
}
$current_errors = $this->get_user_sync_errors();
- $errors = array_merge( $current_errors, $errors );
+ $errors = array_merge( $current_errors, $errors );
update_option( $this->errors_option_name, $errors );
}
@@ -628,6 +647,8 @@ public function set_user_sync_errors( $errors ) {
* Delete the user sync error.
*
* @since x.x.x
+ *
+ * @param string $id The id of the user sync error.
*/
public function delete_user_sync_errors( $id ) {
if ( 'all' === $id ) {
@@ -674,7 +695,8 @@ public function render_user_sync_errors() {
|