PluginProbe
Mailchimp List Subscribe Form / trunk
Mailchimp List Subscribe Form vtrunk
1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3 1.4 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.5 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 All 55 releases
mailchimp / includes / class-mailchimp-analytics-data.php

class-mailchimp-analytics-data.php in Mailchimp List Subscribe Form trunk, at includes/class-mailchimp-analytics-data.php

315 lines 9.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class responsible for form analytics data storage and retrieval.
4 *
5 * @package Mailchimp
6 */
7
8 // Exit if accessed directly.
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit;
11 }
12
13 /**
14 * Class Mailchimp_Analytics_Data
15 */
16 class Mailchimp_Analytics_Data {
17
18 /**
19 * Database version for the analytics table.
20 *
21 * @var string
22 */
23 const DB_VERSION = '1.0.0';
24
25 /**
26 * Initialize the class.
27 */
28 public function init() {
29 add_action( 'wp_ajax_mailchimp_sf_track_form_view', array( $this, 'handle_form_view' ) );
30 add_action( 'wp_ajax_nopriv_mailchimp_sf_track_form_view', array( $this, 'handle_form_view' ) );
31 add_action( 'wp_ajax_mailchimp_sf_get_analytics', array( $this, 'handle_get_analytics' ) );
32 add_action( 'mailchimp_sf_form_submission_success', array( $this, 'track_submission' ) );
33 }
34
35 /**
36 * Get the analytics table name.
37 *
38 * @return string
39 */
40 public static function get_table_name() {
41 global $wpdb;
42 return $wpdb->prefix . 'mailchimp_sf_form_analytics';
43 }
44
45 /**
46 * Create the analytics table.
47 */
48 public static function create_table() {
49 global $wpdb;
50
51 $table_name = self::get_table_name();
52 $charset_collate = $wpdb->get_charset_collate();
53
54 $sql = "CREATE TABLE {$table_name} (
55 id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
56 list_id varchar(20) NOT NULL,
57 form_id varchar(50) NOT NULL DEFAULT '',
58 event_date date NOT NULL,
59 views bigint(20) unsigned NOT NULL DEFAULT 0,
60 submissions bigint(20) unsigned NOT NULL DEFAULT 0,
61 PRIMARY KEY (id),
62 UNIQUE KEY list_form_date (list_id, form_id, event_date)
63 ) {$charset_collate};";
64
65 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
66 dbDelta( $sql );
67
68 update_option( 'mailchimp_sf_analytics_db_version', self::DB_VERSION );
69 }
70
71 /**
72 * Increment the view count for a list on today's date.
73 *
74 * @param string $list_id The list ID.
75 * @param string $form_id The form ID.
76 */
77 public function increment_views( $list_id, $form_id = '' ) {
78 global $wpdb;
79
80 $table_name = self::get_table_name();
81
82 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
83 $result = $wpdb->query(
84 $wpdb->prepare(
85 "INSERT INTO {$table_name} (list_id, form_id, event_date, views, submissions)
86 VALUES (%s, %s, %s, 1, 0)
87 ON DUPLICATE KEY UPDATE views = views + 1",
88 $list_id,
89 $form_id,
90 current_time( 'Y-m-d' )
91 )
92 );
93 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
94
95 if ( false === $result && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
96 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
97 error_log( 'Mailchimp Analytics: Failed to increment views for list_id ' . sanitize_text_field( $list_id ) . '. DB error: ' . $wpdb->last_error );
98 }
99 }
100
101 /**
102 * Increment the submission count for a list on today's date.
103 *
104 * @param string $list_id The list ID.
105 * @param string $form_id The form ID.
106 */
107 public function increment_submissions( $list_id, $form_id = '' ) {
108 global $wpdb;
109
110 $table_name = self::get_table_name();
111
112 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
113 $result = $wpdb->query(
114 $wpdb->prepare(
115 "INSERT INTO {$table_name} (list_id, form_id, event_date, views, submissions)
116 VALUES (%s, %s, %s, 0, 1)
117 ON DUPLICATE KEY UPDATE submissions = submissions + 1",
118 $list_id,
119 $form_id,
120 current_time( 'Y-m-d' )
121 )
122 );
123 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
124
125 if ( false === $result && defined( 'WP_DEBUG' ) && WP_DEBUG ) {
126 // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
127 error_log( 'Mailchimp Analytics: Failed to increment submissions for list_id ' . sanitize_text_field( $list_id ) . '. DB error: ' . $wpdb->last_error );
128 }
129 }
130
131 /**
132 * Get analytics data for a list within a date range.
133 *
134 * @param string $list_id The list ID.
135 * @param string $start_date Start date (Y-m-d).
136 * @param string $end_date End date (Y-m-d).
137 * @return array Array of daily analytics rows.
138 */
139 public function get_analytics_data( $list_id, $start_date, $end_date ) {
140 global $wpdb;
141
142 $table_name = self::get_table_name();
143
144 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
145 $results = $wpdb->get_results(
146 $wpdb->prepare(
147 "SELECT event_date, SUM(views) AS views, SUM(submissions) AS submissions
148 FROM {$table_name}
149 WHERE list_id = %s AND event_date BETWEEN %s AND %s
150 GROUP BY event_date
151 ORDER BY event_date ASC",
152 $list_id,
153 $start_date,
154 $end_date
155 ),
156 ARRAY_A
157 );
158 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
159
160 return $results;
161 }
162
163 /**
164 * Get totals for a list within a date range.
165 *
166 * @param string $list_id The list ID.
167 * @param string $start_date Start date (Y-m-d).
168 * @param string $end_date End date (Y-m-d).
169 * @return array Associative array with total views and submissions.
170 */
171 public function get_totals( $list_id, $start_date, $end_date ) {
172 global $wpdb;
173
174 $table_name = self::get_table_name();
175
176 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
177 $result = $wpdb->get_row(
178 $wpdb->prepare(
179 "SELECT COALESCE(SUM(views), 0) AS total_views, COALESCE(SUM(submissions), 0) AS total_submissions
180 FROM {$table_name}
181 WHERE list_id = %s AND event_date BETWEEN %s AND %s",
182 $list_id,
183 $start_date,
184 $end_date
185 ),
186 ARRAY_A
187 );
188 // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
189
190 if ( ! $result ) {
191 return array(
192 'total_views' => 0,
193 'total_submissions' => 0,
194 );
195 }
196
197 return $result;
198 }
199
200 /**
201 * Handle the AJAX form view tracking request.
202 */
203 public function handle_form_view() {
204 // Verify nonce.
205 if ( ! isset( $_POST['mailchimp_sf_nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['mailchimp_sf_nonce'] ), 'mailchimp_sf_analytics_nonce' ) ) {
206 wp_send_json_error( 'Invalid nonce.', 403 );
207 }
208
209 $list_id = isset( $_POST['list_id'] ) ? sanitize_text_field( wp_unslash( $_POST['list_id'] ) ) : '';
210
211 if ( empty( $list_id ) ) {
212 wp_send_json_error( 'Missing list_id.', 400 );
213 }
214
215 // Ensure the list ID is one of the configured/known lists to prevent
216 // arbitrary IDs from polluting analytics data.
217 if ( ! $this->is_valid_list_id( $list_id ) ) {
218 wp_send_json_error( 'Invalid list_id.', 400 );
219 }
220
221 $this->increment_views( $list_id );
222 wp_send_json_success();
223 }
224
225 /**
226 * Determine whether a list ID is one of the configured/known lists.
227 *
228 * This helps ensure that analytics data is only recorded for legitimate lists
229 * configured within the plugin options.
230 *
231 * @param string $list_id The list ID to validate.
232 * @return bool True if the list ID is known/configured, false otherwise.
233 */
234 private function is_valid_list_id( $list_id ) {
235 if ( empty( $list_id ) ) {
236 return false;
237 }
238
239 $valid_ids = array();
240
241 // Collect list IDs from the stored Mailchimp lists option, if present.
242 $mailchimp_lists = get_option( 'mailchimp_sf_lists' );
243 if ( is_array( $mailchimp_lists ) ) {
244 foreach ( $mailchimp_lists as $list ) {
245 // Handle both scalar IDs and associative array structures.
246 if ( is_string( $list ) || is_int( $list ) ) {
247 $valid_ids[] = (string) $list;
248 } elseif ( is_array( $list ) ) {
249 // Common keys used to store list IDs.
250 foreach ( array( 'id', 'list_id', 'mc_list_id' ) as $key ) {
251 if ( isset( $list[ $key ] ) && ! empty( $list[ $key ] ) ) {
252 $valid_ids[] = (string) $list[ $key ];
253 }
254 }
255 }
256 }
257 }
258
259 // Include the active Mailchimp list ID option, if set.
260 $active_list_id = get_option( 'mc_list_id' );
261 if ( ! empty( $active_list_id ) ) {
262 $valid_ids[] = (string) $active_list_id;
263 }
264
265 // If we have no configured IDs, fail closed and do not treat arbitrary IDs as valid.
266 if ( empty( $valid_ids ) ) {
267 return false;
268 }
269
270 $valid_ids = array_unique( $valid_ids );
271
272 return in_array( (string) $list_id, $valid_ids, true );
273 }
274
275 /**
276 * Handle the AJAX request to fetch analytics data.
277 */
278 public function handle_get_analytics() {
279 if ( ! current_user_can( MCSF_CAP_THRESHOLD ) ) {
280 wp_send_json_error( 'Unauthorized.', 403 );
281 }
282
283 check_ajax_referer( 'mailchimp_sf_analytics_admin_nonce', 'nonce' );
284
285 $list_id = isset( $_POST['list_id'] ) ? sanitize_text_field( wp_unslash( $_POST['list_id'] ) ) : '';
286 $start_date = isset( $_POST['start_date'] ) ? sanitize_text_field( wp_unslash( $_POST['start_date'] ) ) : '';
287 $end_date = isset( $_POST['end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['end_date'] ) ) : '';
288
289 if ( empty( $list_id ) || empty( $start_date ) || empty( $end_date ) ) {
290 wp_send_json_error( 'Missing required parameters.', 400 );
291 }
292
293 $totals = $this->get_totals( $list_id, $start_date, $end_date );
294 $daily = $this->get_analytics_data( $list_id, $start_date, $end_date );
295
296 wp_send_json_success(
297 array(
298 'totals' => $totals,
299 'daily' => $daily,
300 )
301 );
302 }
303
304 /**
305 * Track a successful form submission.
306 *
307 * @param string $list_id The list ID.
308 */
309 public function track_submission( $list_id ) {
310 if ( ! empty( $list_id ) ) {
311 $this->increment_submissions( $list_id );
312 }
313 }
314 }
315