Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions assets/js/mailchimp.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,31 @@
});
}
})(window.jQuery);

/* Form view tracking for analytics */
(function () {
if (!window.mailchimpSF || !window.mailchimpSF.analytics_ajax_url) {
return;
}

const forms = document.querySelectorAll('.mc_signup_form[data-list-id]');
const tracked = {};

for (let i = 0; i < forms.length; i++) {
const listId = forms[i].getAttribute('data-list-id');
if (listId && !tracked[listId]) {
tracked[listId] = true;

const formData = new FormData();
formData.append('action', 'mailchimp_sf_track_form_view');
formData.append('list_id', listId);
formData.append('nonce', window.mailchimpSF.analytics_nonce);

fetch(window.mailchimpSF.analytics_ajax_url, {
method: 'POST',
body: formData,
credentials: 'same-origin',
});
}
}
})();
16 changes: 13 additions & 3 deletions includes/admin/templates/analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,9 +84,19 @@
</div>

<div class="mailchimp-sf-analytics-content" id="mailchimp-sf-analytics-content">
<div class="mailchimp-sf-analytics-placeholder">
<p><?php esc_html_e( 'Select a date range and list to view analytics.', 'mailchimp' ); ?></p>
</div>
<?php
$analytics_data = new Mailchimp_Analytics_Data();
$end_date = current_time( 'Y-m-d' );
$start_date = gmdate( 'Y-m-d', strtotime( '-30 days' ) );

$totals = $analytics_data->get_totals( $current_list, $start_date, $end_date );
$daily = $analytics_data->get_analytics_data( $current_list, $start_date, $end_date );
?>
<h3><?php esc_html_e( 'Totals (Last 30 days)', 'mailchimp' ); ?></h3>
<pre><?php print_r( $totals ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r ?></pre>

<h3><?php esc_html_e( 'Daily Breakdown', 'mailchimp' ); ?></h3>
<pre><?php print_r( $daily ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r ?></pre>
</div>

<?php if ( $dc ) : ?>
Expand Down
2 changes: 1 addition & 1 deletion includes/blocks/mailchimp/markup.php
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ function ( $single_list ) {
}
?>
<div id="mc_signup_<?php echo esc_attr( $form_id ); ?>">
<form method="post" action="#mc_signup_<?php echo esc_attr( $form_id ); ?>" id="mc_signup_form_<?php echo esc_attr( $form_id ); ?>" class="mc_signup_form">
<form method="post" action="#mc_signup_<?php echo esc_attr( $form_id ); ?>" id="mc_signup_form_<?php echo esc_attr( $form_id ); ?>" class="mc_signup_form" data-list-id="<?php echo esc_attr( $list_id ); ?>">
<input type="hidden" class="mc_submit_type" name="mc_submit_type" value="html" />
<input type="hidden" name="mcsf_action" value="mc_submit_signup_form" />
<input type="hidden" name="mailchimp_sf_list_id" value="<?php echo esc_attr( $list_id ); ?>" />
Expand Down
218 changes: 218 additions & 0 deletions includes/class-mailchimp-analytics-data.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
<?php
/**
* Class responsible for form analytics data storage and retrieval.
*
* @package Mailchimp
*/

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* Class Mailchimp_Analytics_Data
*/
class Mailchimp_Analytics_Data {

/**
* Database version for the analytics table.
*
* @var string
*/
const DB_VERSION = '1.0';

/**
* Initialize the class.
*/
public function init() {
add_action( 'wp_ajax_mailchimp_sf_track_form_view', array( $this, 'handle_form_view' ) );
add_action( 'wp_ajax_nopriv_mailchimp_sf_track_form_view', array( $this, 'handle_form_view' ) );
add_action( 'mailchimp_sf_form_submission_success', array( $this, 'track_submission' ) );
}
Comment on lines +28 to +33
Copy link

Copilot AI Mar 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces new analytics behaviors (AJAX endpoint for view tracking, DB writes, and a new submission-success hook) but doesn’t add/extend automated coverage. There is an existing Cypress suite under tests/cypress; adding at least one e2e test that verifies view tracking + submission tracking increments the expected counts would help prevent regressions.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +33
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New analytics behavior (AJAX endpoints + view/submission tracking) isn’t covered by existing Cypress tests. Since the repo already has e2e coverage for the Analytics admin page, add tests that (1) trigger a form view POST and assert the analytics table/counts change, and (2) submit a form successfully and assert submissions increment (and invalid submissions do not).

Copilot uses AI. Check for mistakes.

/**
* Get the analytics table name.
*
* @return string
*/
public static function get_table_name() {
global $wpdb;
return $wpdb->prefix . 'mailchimp_sf_form_analytics';
}

/**
* Create the analytics table.
*/
public static function create_table() {
global $wpdb;

$table_name = self::get_table_name();
$charset_collate = $wpdb->get_charset_collate();

$sql = "CREATE TABLE {$table_name} (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
list_id varchar(20) NOT NULL,
form_id varchar(50) NOT NULL DEFAULT '',
event_date date NOT NULL,
views bigint(20) unsigned NOT NULL DEFAULT 0,
submissions bigint(20) unsigned NOT NULL DEFAULT 0,
PRIMARY KEY (id),
UNIQUE KEY list_form_date (list_id, form_id, event_date)
) {$charset_collate};";

require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta( $sql );

update_option( 'mailchimp_sf_analytics_db_version', self::DB_VERSION );
}

/**
* Increment the view count for a list on today's date.
*
* @param string $list_id The list ID.
* @param string $form_id The form ID.
*/
public function increment_views( $list_id, $form_id = '' ) {
global $wpdb;

$table_name = self::get_table_name();

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query(
$wpdb->prepare(
"INSERT INTO {$table_name} (list_id, form_id, event_date, views, submissions)
VALUES (%s, %s, %s, 1, 0)
ON DUPLICATE KEY UPDATE views = views + 1",
$list_id,
$form_id,
current_time( 'Y-m-d' )
)
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}

/**
* Increment the submission count for a list on today's date.
*
* @param string $list_id The list ID.
* @param string $form_id The form ID.
*/
public function increment_submissions( $list_id, $form_id = '' ) {
global $wpdb;

$table_name = self::get_table_name();

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$wpdb->query(
$wpdb->prepare(
"INSERT INTO {$table_name} (list_id, form_id, event_date, views, submissions)
VALUES (%s, %s, %s, 0, 1)
ON DUPLICATE KEY UPDATE submissions = submissions + 1",
$list_id,
$form_id,
current_time( 'Y-m-d' )
)
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
}

/**
* Get analytics data for a list within a date range.
*
* @param string $list_id The list ID.
* @param string $start_date Start date (Y-m-d).
* @param string $end_date End date (Y-m-d).
* @return array Array of daily analytics rows.
*/
public function get_analytics_data( $list_id, $start_date, $end_date ) {
global $wpdb;

$table_name = self::get_table_name();

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$results = $wpdb->get_results(
$wpdb->prepare(
"SELECT event_date, SUM(views) AS views, SUM(submissions) AS submissions
FROM {$table_name}
WHERE list_id = %s AND event_date BETWEEN %s AND %s
GROUP BY event_date
ORDER BY event_date ASC",
$list_id,
$start_date,
$end_date
),
ARRAY_A
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared

return $results;
}

/**
* Get totals for a list within a date range.
*
* @param string $list_id The list ID.
* @param string $start_date Start date (Y-m-d).
* @param string $end_date End date (Y-m-d).
* @return array Associative array with total views and submissions.
*/
public function get_totals( $list_id, $start_date, $end_date ) {
global $wpdb;

$table_name = self::get_table_name();

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
$result = $wpdb->get_row(
$wpdb->prepare(
"SELECT COALESCE(SUM(views), 0) AS total_views, COALESCE(SUM(submissions), 0) AS total_submissions
FROM {$table_name}
WHERE list_id = %s AND event_date BETWEEN %s AND %s",
$list_id,
$start_date,
$end_date
),
ARRAY_A
);
// phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared

if ( ! $result ) {
return array(
'total_views' => 0,
'total_submissions' => 0,
);
}

return $result;
}

/**
* Handle the AJAX form view tracking request.
*/
public function handle_form_view() {
// Verify nonce.
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), 'mailchimp_sf_analytics_nonce' ) ) {
wp_send_json_error( 'Invalid nonce.', 403 );
}

$list_id = isset( $_POST['list_id'] ) ? sanitize_text_field( wp_unslash( $_POST['list_id'] ) ) : '';

if ( empty( $list_id ) ) {
wp_send_json_error( 'Missing list_id.', 400 );
}

$this->increment_views( $list_id );
wp_send_json_success();
}

/**
* Track a successful form submission.
*
* @param string $list_id The list ID.
*/
public function track_submission( $list_id ) {
if ( ! empty( $list_id ) ) {
$this->increment_submissions( $list_id );
}
}
}
1 change: 0 additions & 1 deletion includes/class-mailchimp-analytics.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,5 @@ public function enqueue_scripts( $hook_suffix ) {
MCSF_VER,
true
);

}
}
7 changes: 7 additions & 0 deletions includes/class-mailchimp-form-submission.php
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,13 @@ public function handle_form_submission() {
$message = __( 'Success, you\'ve been signed up! Please look for our confirmation email.', 'mailchimp' );
}

/**
* Fires after a successful form submission.
*
* @param string $list_id The list ID the user subscribed to.
*/
do_action( 'mailchimp_sf_form_submission_success', $list_id );

// Return success message.
return $message;
}
Expand Down
10 changes: 10 additions & 0 deletions mailchimp.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ function () {
$analytics = new Mailchimp_Analytics();
$analytics->init();

// Analytics data class.
require_once plugin_dir_path( __FILE__ ) . 'includes/class-mailchimp-analytics-data.php';
$analytics_data = new Mailchimp_Analytics_Data();
$analytics_data->init();

// Create analytics table on activation.
register_activation_hook( __FILE__, array( 'Mailchimp_Analytics_Data', 'create_table' ) );

// Deprecated functions.
require_once plugin_dir_path( __FILE__ ) . 'includes/mailchimp-deprecated-functions.php';

Expand Down Expand Up @@ -171,6 +179,8 @@ function mailchimp_sf_load_resources() {
array(
'ajax_url' => trailingslashit( home_url() ),
'phone_validation_error' => esc_html__( 'Please enter a valid phone number.', 'mailchimp' ),
'analytics_ajax_url' => admin_url( 'admin-ajax.php' ),
'analytics_nonce' => wp_create_nonce( 'mailchimp_sf_analytics_nonce' ),
)
);

Expand Down
6 changes: 6 additions & 0 deletions mailchimp_upgrade.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ function mailchimp_version_check() {
mailchimp_update_1_7_0();
}

// Create analytics table if it doesn't exist.
$analytics_db_version = get_option( 'mailchimp_sf_analytics_db_version' );
if ( false === $analytics_db_version || version_compare( Mailchimp_Analytics_Data::DB_VERSION, $analytics_db_version, '>' ) ) {
Mailchimp_Analytics_Data::create_table();
}
Comment on lines +29 to +33
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This table-creation block won’t run if mailchimp_version_check() returns early when MCSF_VER === get_option('mc_version'). That means a site can end up with no analytics table if the mc_version option is already current (e.g., version string unchanged, or option manually set) but mailchimp_sf_analytics_db_version is missing/outdated. Consider moving the analytics DB version check above the early return, or adjusting the early return condition to also account for the analytics DB version option.

Copilot uses AI. Check for mistakes.

update_option( 'mc_version', MCSF_VER );
}

Expand Down
2 changes: 1 addition & 1 deletion mailchimp_widget.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function mailchimp_sf_signup_form( $args = array() ) {
?>

<div id="mc_signup_<?php echo esc_attr( $form_id ); ?>">
<form method="post" action="#mc_signup_<?php echo esc_attr( $form_id ); ?>" id="mc_signup_form_<?php echo esc_attr( $form_id ); ?>" class="mc_signup_form">
<form method="post" action="#mc_signup_<?php echo esc_attr( $form_id ); ?>" id="mc_signup_form_<?php echo esc_attr( $form_id ); ?>" class="mc_signup_form" data-list-id="<?php echo esc_attr( $list_id ); ?>">
<input type="hidden" class="mc_submit_type" name="mc_submit_type" value="html" />
<input type="hidden" name="mcsf_action" value="mc_submit_signup_form" />
<?php wp_nonce_field( 'mc_submit_signup_form', '_mc_submit_signup_form_nonce', false ); ?>
Expand Down
Loading