PluginProbe
Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) / trunk
Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) vtrunk
9.1.2 9.1.1 9.1.0 9.0.2 9.0.1 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.1.0 1.1.1 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 All 153 releases
wp-analytify / classes / analytify-email / scheduler.php

scheduler.php in Analytify – Google Analytics Dashboard For WordPress (GA4 analytics tracking) trunk, at classes/analytify-email/scheduler.php

569 lines 21.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php // phpcs:ignore Squiz.Commenting.FileComment.Missing -- File doc comment exists
2 /**
3 * Analytify Email Scheduler Trait
4 *
5 * This trait contains all the scheduling and cron functionality for the Analytify Email system.
6 * It was created to separate scheduling logic from other email operations, keeping the code
7 * organized and maintainable.
8 *
9 * PURPOSE:
10 * - Manages email cron scheduling and timing (WP daily cron; weekly/monthly are day-matched)
11 * - Weekly: last 7 days ending now; disabled when weekday is "Select Day" (false)
12 * - Monthly last_day: send on last calendar day (site TZ); report = 1st through that day
13 * - Monthly numeric day: send that day (or last day if day does not exist, e.g. 31 in Feb); report = 30 days inclusive ending that day
14 * - Scheduling uses the site timezone via wp_timezone(); email range labels use wp_date()
15 *
16 * @package WP_Analytify
17 * @subpackage Email
18 * @since 8.0.0
19 * @version 9.0.0
20 */
21
22 trait Analytify_Email_Scheduler {
23
24 /**
25 * Callback function for cron job - sends scheduled email reports.
26 *
27 * @return void
28 * @since 1.0.0
29 * @version 9.1.0
30 */
31 public function callback_on_cron_time() {
32 // Return if no profile selected.
33 $profile = $GLOBALS['WP_ANALYTIFY']->settings->get_option( 'profile_for_dashboard', 'wp-analytify-profile' ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Variable name is acceptable for this context
34 if ( empty( $profile ) ) {
35 return;
36 }
37
38 // Return if reports are off.
39 $disable_emails = $this->WP_ANALYTIFY->settings->get_option( 'disable_email_reports', 'wp-analytify-email' ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Variable name is acceptable for this context
40 if ( 'on' === $disable_emails ) {
41 return;
42 }
43
44 // stop TranslatePress to translate the emails.
45 add_filter( 'trp_stop_translating_page', '__return_true' );
46
47 $wp_analytify = $GLOBALS['WP_ANALYTIFY'];
48 $site_url = site_url();
49 $when_to_send_report = $this->when_to_send_report();
50
51 foreach ( $when_to_send_report as $when ) {
52 $is_custom_range_report = false;
53
54 if ( 'week' === $when ) {
55 $report_of = 'Weekly';
56 $start_date_val = strtotime( '-1 week' );
57 $end_date_val = strtotime( 'now' );
58 } elseif ( 'yesterday' === $when ) {
59 $report_of = 'Yesterday';
60 $start_date_val = strtotime( 'yesterday' );
61 $end_date_val = strtotime( 'yesterday' );
62 } elseif ( 'custom_range' === $when ) {
63 if ( ! function_exists( 'analytify_email_get_validated_custom_range_bounds' ) ) {
64 continue;
65 }
66 $cr_bounds = analytify_email_get_validated_custom_range_bounds( $wp_analytify->settings, true );
67 if ( null === $cr_bounds ) {
68 continue;
69 }
70 $start_date_val = strtotime( $cr_bounds['start'] . ' 00:00:00 UTC' );
71 $end_date_val = strtotime( $cr_bounds['end'] . ' 00:00:00 UTC' );
72 if ( ! $start_date_val || ! $end_date_val ) {
73 continue;
74 }
75 $report_of = __( 'Custom Range', 'wp-analytify' );
76 } elseif ( 'month' === $when ) {
77 $report_of = 'Monthly';
78 $start_date_val = strtotime( '-1 month' );
79 $end_date_val = strtotime( 'now' );
80 } else {
81 // Unknown schedule token; do not fall back to a rolling month window.
82 continue;
83 }
84
85 $start_date = gmdate( 'Y-m-d', $start_date_val );
86 $end_date = gmdate( 'Y-m-d', $end_date_val );
87
88 // Monthly sends only: Pro replaces the rolling month window (helpers loaded in analytify-email.php).
89 // Do not apply to yesterday or explicit custom_range — those dates would be overwritten.
90 if ( 'month' === $when && function_exists( 'analytify_email_scheduled_report_period' ) ) {
91 $custom_period = analytify_email_scheduled_report_period( $wp_analytify->settings );
92 if ( is_array( $custom_period ) && isset( $custom_period['start'], $custom_period['end'] ) ) {
93 $start_date = $custom_period['start'];
94 $end_date = $custom_period['end'];
95 $start_date_val = strtotime( $start_date . ' 00:00:00 UTC' );
96 $end_date_val = strtotime( $end_date . ' 00:00:00 UTC' );
97 $report_of = __( 'Custom Range', 'wp-analytify' );
98 $is_custom_range_report = true;
99 }
100 }
101
102 if ( function_exists( 'wp_date' ) ) {
103 $report_range_display = wp_date( 'M j, Y', $start_date_val ) . ' - ' . wp_date( 'M j, Y', $end_date_val );
104 } else {
105 $report_range_display = gmdate( 'M j, Y', $start_date_val ) . ' - ' . gmdate( 'M j, Y', $end_date_val );
106 }
107
108 $date1 = date_create( $start_date );
109 $date2 = date_create( $end_date );
110 if ( 'yesterday' === $when ) {
111 $different = '1 ' . analytify__( 'days', 'wp-analytify' );
112 $compare_end_date = gmdate( 'Y-m-d', strtotime( '-2 days' ) );
113 $compare_start_date = $compare_end_date;
114 } elseif ( $date1 && $date2 ) {
115 $diff = date_diff( $date2, $date1 );
116 $different = $diff->format( '%a' ) . ' ' . analytify__( 'days', 'wp-analytify' );
117
118 $compare_start_date = strtotime( $start_date . ' ' . $diff->format( '%R%a' ) . ' days' );
119 $compare_start_date = $compare_start_date ? gmdate( 'Y-m-d', $compare_start_date ) : $start_date;
120 } else {
121 $different = '0 ' . analytify__( 'days', 'wp-analytify' );
122 $compare_start_date = $start_date;
123 }
124 if ( 'yesterday' !== $when ) {
125 $compare_end_date = $start_date;
126 }
127
128 $_logo_id = $wp_analytify->settings->get_option( 'analytify_email_logo', 'wp-analytify-email' );
129
130 if ( $_logo_id ) {
131 $_logo_link_array = wp_get_attachment_image_src( $_logo_id, array( 150, 150 ) );
132 $logo_link = $_logo_link_array ? $_logo_link_array[0] : '';
133 } else {
134 $logo_link = ( defined( 'ANALYTIFY_IMAGES_PATH' ) ? ANALYTIFY_IMAGES_PATH : 'https://analytify.io/assets/email/' ) . 'logo.png';
135 }
136
137 $emails = $wp_analytify->settings->get_option( 'analytify_email_user_email', 'wp-analytify-email' );
138 $emails_array = array();
139
140 if ( ! empty( $emails ) ) {
141 if ( ! is_array( $emails ) ) {
142 $emails_array = explode( ',', $emails );
143 } else {
144 $emails_array = $emails;
145 }
146 }
147
148 $subject = $wp_analytify->settings->get_option( 'analytify_email_subject', 'wp-analytify-email' );
149
150 if ( ! $subject ) {
151 $protocols = array( 'https://', 'https://www', 'http://', 'http://www.', 'www.' );
152 $site_url = str_replace( $protocols, '', get_home_url() );
153
154 $site_url = sanitize_text_field( $site_url );
155
156 if ( 'week' === $when ) {
157 // translators: Weekly engagement.
158 $subject = sprintf( __( 'Weekly Engagement Summary of %s', 'wp-analytify' ), $site_url );
159 } elseif ( $is_custom_range_report ) {
160 // translators: Default email subject when the report uses a saved custom date range. %s: site hostname.
161 $subject = sprintf( __( 'Custom Analytics Report for %s', 'wp-analytify' ), $site_url );
162 } elseif ( 'month' === $when ) {
163 // translators: Monthly engagement.
164 $subject = sprintf( __( 'Monthly Engagement Summary of %s', 'wp-analytify' ), $site_url );
165 } elseif ( 'yesterday' === $when ) {
166 // translators: Previous day engagement.
167 $subject = sprintf( __( 'Yesterday\'s Engagement Summary of %s', 'wp-analytify' ), $site_url );
168 } elseif ( 'custom_range' === $when ) {
169 // translators: Default subject for scheduled email when schedule type is Custom Range. %s: site hostname.
170 $subject = sprintf( __( 'Analytics Report for %s', 'wp-analytify' ), $site_url );
171 }
172 }
173
174 $_from_name = $wp_analytify->settings->get_option( 'analytiy_from_name', 'wp-analytify-email' );
175 $_from_name = ! empty( $_from_name ) ? $_from_name : 'Analytify Notifications';
176 $_from_email = $wp_analytify->settings->get_option( 'analytiy_from_email', 'wp-analytify-email' );
177 $_from_email = ! empty( $_from_email ) ? $_from_email : 'no-reply@analytify.io';
178
179 foreach ( $emails_array as $email_group ) {
180 if ( is_array( $email_group ) ) {
181 $email_group_name = trim( $email_group['name'] );
182 $name = ! empty( $email_group_name ) ? ' ' . esc_html( $email_group_name ) : '';
183 $email_address = sanitize_email( $email_group['email'] );
184 } else {
185 $name = '';
186 $email_address = sanitize_email( $email_group );
187 }
188
189 $headers = array(
190 'From: ' . $_from_name . ' <' . $_from_email . '>',
191 // 'To: ' . $email_address, // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- This is intentionally commented out
192 'Content-Type: text/html; charset=UTF-8',
193 );
194
195 $custom_message = $wp_analytify->settings->get_option( 'analytify_note_text', 'wp-analytify-email' );
196
197 if ( empty( $custom_message ) ) {
198 $custom_message = __( 'Please find below your Google Analytics report for the noted period.', 'wp-analytify' );
199 }
200 /**
201 * Filter to modify the custom email message in Analytify.
202 *
203 * This filter allows developers to customize the email message content
204 * before it is processed or sent.
205 *
206 * @since 7.0.0
207 *
208 * @param string $custom_message The default custom email message.
209 * @return string The modified custom email message.
210 */
211
212 $custom_message = apply_filters( 'analytify_custom_email_message', $custom_message );
213
214 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- This is an email template with inline styles
215 $message = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
216 <html xmlns="http://www.w3.org/1999/xhtml">
217 <head>
218 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
219 <meta name="viewport" content="width=device-width, initial-scale=1" />
220 <meta name="x-apple-disable-message-reformatting" />
221 <title>Analytify</title>
222 <meta name="color-scheme" content="light dark">
223 <meta name="supported-color-schemes" content="light dark">
224 <style type="text/css">
225 @media screen and (max-width: 620px) {
226 .main-table {
227 width: 100% !important;
228 padding-left: 20px !important;
229 padding-right: 20px !important;
230 }
231 }
232
233 @media screen and (max-width: 560px) {
234 .box-table>tbody>tr>td {
235 width: 100% !important;
236 display: block !important;
237 margin-bottom: 10px !important;
238 }
239
240 .session-table>table {
241 display: block !important;
242 width: 100% !important;
243 }
244
245 .session-table>table>tbody {
246 display: block !important;
247 width: 100% !important;
248 }
249
250 .session-table>table>tbody>tr {
251 display: block !important;
252 width: 96% !important;
253 margin: 10px 2% 10px !important;
254 }
255
256 .os-table>td,
257 .keywords-table>td {
258 display: block;
259 width: 100% !important;
260 }
261
262 .geographic-table>tbody>tr>td {
263 display: block !important;
264 width: 100% !important;
265 }
266
267 .user-data>table>tbody>tr>td {
268 padding: 10px !important;
269 }
270
271 .mobile-hide {
272 display: none !important;
273 }
274
275 .main-table>tbody>tr>td {
276 padding: 10px !important;
277 }
278
279 .user-data>table>tbody>tr>td img {
280 margin-left: 0 !important;
281 }
282 }
283 @media (prefers-color-scheme: dark ) {
284 body, [bgcolor="#ffffff"],[bgcolor="#f5f9ff"],[bgcolor="#f9fafa"], [bgcolor="#f3f7fa"], .session-table, .session-table tr td{
285 background-color: #000 !important;
286 }
287 table[bgcolor="#f9fafa"]>tbody>tr>td, .os-table td,.geographic-table{
288 background-color: #000000 !important;
289 }
290 .session-table tr td{
291 border-color: #fff !important;
292 color: #fff !important;
293 }
294
295 table tbody td [color="#313133"],
296 table tbody td [color="#383b3d"],
297 table tbody td [color="#444"],
298 table tbody td [color="#444444"],
299 table tbody td [color="#848484"],
300 table tbody td [color="#909090"]{
301 color: #fff !important;
302 }
303 table tbody td hr{
304 border-top:1px solid #fff !important;
305 }
306 }
307 </style>
308 </head>
309
310 <body style="margin: 0;padding: 0; background: #f3f7fa; " bgcolor="#f3f7fa">
311 <table cellpadding="0" cellspacing="0" border="0" width="100%" align="center" bgcolor="#f3f7fa">
312
313 <tr>
314 <td valign="top" style="padding-bottom:95px">
315 <table cellpadding="0" cellspacing="0" border="0" width="600" align="center" class="main-table">
316
317 <tr>
318 <td style="padding: 22px 35px;">
319 <table width="100%" cellpadding="0" cellspacing="0" align="center">
320 <tr>
321 <td align="left"><a href="' . $site_url . '"><img src="' . $logo_link . '" alt="analytify"/></a></td>
322 <td align="right" style="font: normal 15px \'Roboto\', Arial, Helvetica, sans-serif; line-height: 1.5;">
323 <font color="#444444">' . esc_html(
324 sprintf(
325 /* translators: %s: period label, e.g. Weekly, Monthly, Yesterday, Custom Range. */
326 __( '%s Report', 'wp-analytify' ),
327 $report_of
328 )
329 ) . '</font><br>
330 <font color="#848484">' . esc_html( $report_range_display ) . '</font><br />
331 <font color="#848484"><a href="' . get_home_url() . '">' . get_home_url() . '</a></font>
332 </td>
333 </tr>
334 </table>
335 </td>
336 </tr>
337
338 <tr>
339 <td style="padding: 0 15px;">
340 <table width="100%" cellpadding="0" cellspacing="0" align="center">
341 <tr>
342 <td valign="top">
343 <table width="100%" cellspacing="0" cellpadding="0" border="0" align="center" bgcolor="#ffffff">
344 <tr>
345 <td style="font: 400 18px \'Roboto slab\', Arial, Helvetica, sans-serif; padding: 25px 20px 11px 20px;">
346 <font color="#444444">' . analytify__( 'Hi' ) . $name . ',</font>
347 </td>
348 </tr>
349 <tr>
350 <td style="font: normal 14px \'Roboto\', Arial, Helvetica, sans-serif; padding: 0px 20px 0px 20px;">
351 <font color="#848484">
352 ' . wp_kses_post( $custom_message ) . '
353 </font>
354 </td>
355 </tr>
356 </table>
357 </td>
358 </tr>';
359
360 $selected_stats = ! empty( $wp_analytify->settings->get_option( 'analytify_email_stats', 'wp-analytify-email' ) ) ? $wp_analytify->settings->get_option( 'analytify_email_stats', 'wp-analytify-email' ) : array( 'show-overall-general' );
361
362 // General Stats.
363 if ( is_array( $selected_stats ) && in_array( 'show-overall-general', $selected_stats, true ) ) {
364
365 if ( class_exists( 'WPANALYTIFY_Utils' ) ) {
366 $ga_mode = WPANALYTIFY_Utils::get_ga_mode();
367 if ( 'ga4' === $ga_mode ) {
368
369 $stats = $wp_analytify->get_reports(
370 'analytify-email-general-stats',
371 array(
372 'sessions',
373 'totalUsers',
374 'bounceRate',
375 'screenPageViewsPerSession',
376 'screenPageViews',
377 'engagedSessions',
378 'newUsers',
379 'averageSessionDuration',
380 'userEngagementDuration',
381 ),
382 array(
383 'start' => $start_date,
384 'end' => $end_date,
385 ),
386 array(
387 'date',
388 ),
389 array(
390 'type' => 'dimension',
391 'order' => 'desc',
392 'name' => 'date',
393 ),
394 array()
395 );
396
397 $old_stats = $wp_analytify->get_reports(
398 'analytify-email-general-compare-stats',
399 array(
400 'sessions',
401 'totalUsers',
402 'bounceRate',
403 'screenPageViewsPerSession',
404 'screenPageViews',
405 'engagedSessions',
406 'newUsers',
407 'averageSessionDuration',
408 'userEngagementDuration',
409 ),
410 array(
411 'start' => $compare_start_date,
412 'end' => $compare_end_date,
413 ),
414 array(
415 'date',
416 ),
417 array(
418 'type' => 'dimension',
419 'order' => 'desc',
420 'name' => 'date',
421 ),
422 array()
423 );
424
425 if ( ! function_exists( 'pa_email_include_general' ) ) {
426 $plugin_dir = defined( 'WP_ANALYTIFY_PLUGIN_DIR' ) ? WP_ANALYTIFY_PLUGIN_DIR : dirname( dirname( __DIR__ ) );
427 include $plugin_dir . '/views/email/general-stats.php';
428 }
429
430 $message .= pa_email_include_general( $wp_analytify, $stats, $old_stats, $different );
431 }
432 }
433 }
434
435 $dates = array(
436 'start_date' => $start_date,
437 'end_date' => $end_date,
438 );
439
440 // Get pro settings options.
441 $message = apply_filters( 'wp_analytify_email_on_cron_time', $message, $selected_stats, $dates );
442
443 $message .= '
444 </table>
445 </td>
446 </tr>
447 </table>
448 </td>
449 </tr>
450 </table>
451 </td>
452 </tr>
453 </table>
454 </body>
455 </html>';
456
457 wp_mail( $email_address, $subject, $message, $headers );
458 }
459 }
460 }
461
462 /**
463 * Determine when to send email reports based on current time and settings.
464 *
465 * @return array<string, mixed> Array of report types to send.
466 * @since 1.0.0
467 */
468 public function when_to_send_report() {
469 $when_to_send_email = array();
470 // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce verification is handled in the callback function
471 $is_test_email = isset( $_POST['test_email'] );
472
473 if ( class_exists( 'WP_Analytify_Email' ) || class_exists( 'WP_Analytify_Addon_Email' ) ) {
474 $time_settings = function_exists( 'analytify_email_get_schedule_cron_time_array' )
475 ? analytify_email_get_schedule_cron_time_array( $GLOBALS['WP_ANALYTIFY']->settings )
476 : $GLOBALS['WP_ANALYTIFY']->settings->get_option( 'analytif_email_cron_time', 'wp-analytify-email' );
477 if ( ! is_array( $time_settings ) ) {
478 $time_settings = array();
479 }
480 $week_raw = isset( $time_settings['week'] ) ? $time_settings['week'] : '';
481 $month_raw = isset( $time_settings['month'] ) ? $time_settings['month'] : '';
482 $week_date = ( $week_raw && 'false' !== $week_raw ) ? $week_raw : '';
483 $month_date = ( $month_raw && 'false' !== $month_raw ) ? trim( (string) $month_raw ) : '';
484
485 if ( isset( $time_settings['yesterday'] ) && 'enabled' === $time_settings['yesterday'] ) {
486 $when_to_send_email[] = 'yesterday';
487 }
488
489 $custom_on = isset( $time_settings['custom_range'] ) && 'enabled' === $time_settings['custom_range'];
490 if ( $custom_on ) {
491 $cr_s = $GLOBALS['WP_ANALYTIFY']->settings->get_option( 'analytify_email_custom_range_start', 'wp-analytify-email' );
492 $cr_e = $GLOBALS['WP_ANALYTIFY']->settings->get_option( 'analytify_email_custom_range_end', 'wp-analytify-email' );
493 if ( ! empty( $cr_s ) && ! empty( $cr_e ) ) {
494 $when_to_send_email[] = 'custom_range';
495 }
496 }
497 } else {
498 $week_date = 'Monday';
499 $month_date = false;
500 }
501
502 /**
503 * PHPUnit may set `$GLOBALS['analytify_tests_email_schedule_clock']` to an array with optional
504 * keys `l` (weekday), `j` (day of month), `t` (days in month) before calling schedule logic.
505 *
506 * Filters the calendar values used for weekly / monthly schedule matching in email reports.
507 *
508 * Return an array with optional keys: `l` weekday name (e.g. Wednesday), `j` day of month (1-31),
509 * `t` number of days in the month. Omit a key to keep the live `gmdate()` value for that part.
510 *
511 * @since 9.0.1
512 *
513 * @param array<string, string|int>|null $clock Optional clock override.
514 */
515 $clock = null;
516 if ( isset( $GLOBALS['analytify_tests_email_schedule_clock'] ) && is_array( $GLOBALS['analytify_tests_email_schedule_clock'] ) ) {
517 $clock = $GLOBALS['analytify_tests_email_schedule_clock'];
518 }
519 if ( null === $clock ) {
520 $clock = apply_filters( 'analytify_email_schedule_clock', null );
521 }
522 if ( is_array( $clock ) ) {
523 $current_day = isset( $clock['l'] ) ? (string) $clock['l'] : gmdate( 'l' );
524 $current_date = isset( $clock['j'] ) ? (string) (int) $clock['j'] : gmdate( 'j' );
525 $last_day_of_month = isset( $clock['t'] ) ? (string) (int) $clock['t'] : gmdate( 't' );
526 } else {
527 $current_day = gmdate( 'l' ); // Sunday through Saturday.
528 $current_date = gmdate( 'j' ); // Day of the month without leading zeros.
529 $last_day_of_month = gmdate( 't' ); // Number of days in the given month.
530 }
531
532 if ( '' !== $week_date && $current_day === $week_date ) {
533 $when_to_send_email[] = 'week';
534 }
535
536 // Monthly: specific calendar day, numeric last-day match, or Pro "Last Day" option.
537 if ( $month_date ) {
538 $is_last_day_token = (bool) preg_match( '/^last[_\s-]?day$/i', (string) $month_date );
539 if ( $is_last_day_token ) {
540 if ( (string) $current_date === (string) $last_day_of_month ) {
541 $when_to_send_email[] = 'month';
542 }
543 } elseif ( (string) $last_day_of_month === (string) $month_date ) {
544 // Saved day equals month length: send on any day (legacy monthly "last day of month" numeric form).
545 $when_to_send_email[] = 'month';
546 } elseif ( (string) $current_date === (string) $month_date ) {
547 $when_to_send_email[] = 'month';
548 }
549 }
550
551 // Test Email: use the same rules as cron. If nothing would run today (e.g. weekly not due),
552 // fall back to a preview so the button still sends something.
553 if ( $is_test_email && empty( $when_to_send_email ) ) {
554 if ( class_exists( 'WP_Analytify_Email' ) || class_exists( 'WP_Analytify_Addon_Email' ) ) {
555 $when_to_send_email[] = 'month';
556 } else {
557 $when_to_send_email[] = 'week';
558 }
559 }
560
561 // Convert to associative array with meaningful keys.
562 $result = array();
563 foreach ( $when_to_send_email as $index => $value ) {
564 $result[ 'schedule_' . $index ] = $value;
565 }
566 return $result;
567 }
568 }
569