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 / single-send.php

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

424 lines 16.1 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 Single Send Trait
4 *
5 * This trait contains all the single post email sending functionality for the Analytify Email system.
6 * It was created to separate single email logic from other email operations, keeping the code
7 * organized and maintainable.
8 *
9 * PURPOSE:
10 * - Handles AJAX requests for single post emails
11 * - Manages single post email sending logic
12 * - Processes email data for individual posts
13 * - Handles email validation and processing
14 *
15 * @package WP_Analytify
16 * @subpackage Email
17 * @since 8.0.0
18 */
19
20 trait Analytify_Email_Single_Send {
21
22 /**
23 * Send Email Stats for Single Page/Post (returns immediately with success; sends email after response flush).
24 *
25 * @since 1.2.0
26 * @since 9.0.0 Sends JSON success response, flushes to client, then runs email in same request so mail is sent reliably (e.g. on localhost where background self-request often fails).
27 * @return void
28 */
29 public function send_analytics_email() {
30
31 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
32 $recipient_email = isset( $_POST['recipient_email'] ) ? sanitize_email( wp_unslash( $_POST['recipient_email'] ) ) : '';
33 $is_access_level = $this->WP_ANALYTIFY->settings->get_option( 'show_analytics_roles_back_end', 'wp-analytify-admin', array( 'administrator' ) ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Variable name is acceptable for this context
34
35 $is_access_level = (bool) $this->WP_ANALYTIFY->pa_check_roles( $is_access_level ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Variable name is acceptable for this context
36
37 if ( ! wp_verify_nonce( $nonce, 'analytify-single-post-email' ) || ! $is_access_level ) {
38 wp_die( 'Sorry, you are not allowed to do that.', 403 );
39 }
40
41 $start_date = isset( $_POST['start_date'] ) ? sanitize_text_field( wp_unslash( $_POST['start_date'] ) ) : '';
42 $end_date = isset( $_POST['end_date'] ) ? sanitize_text_field( wp_unslash( $_POST['end_date'] ) ) : '';
43 $post_id = isset( $_POST['post_id'] ) ? (int) sanitize_text_field( wp_unslash( $_POST['post_id'] ) ) : 0;
44
45 // Send success response so Preview/Response are not empty and UI shows "Email Report Sent!".
46 $body = wp_json_encode( array( 'success' => true ) );
47 if ( ! headers_sent() ) {
48 status_header( 200 );
49 header( 'Content-Type: application/json; charset=' . get_option( 'blog_charset' ) );
50 header( 'Content-Length: ' . (string) strlen( $body ) );
51 }
52 echo $body; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- JSON-encoded response.
53 // Flush so the client receives the response immediately; then send email in same request.
54 while ( ob_get_level() ) {
55 ob_end_flush();
56 }
57 flush();
58 if ( function_exists( 'fastcgi_finish_request' ) ) {
59 fastcgi_finish_request();
60 }
61
62 $this->do_send_single_analytics_email( $post_id, $start_date, $end_date, $recipient_email );
63 exit;
64 }
65
66 /**
67 * Background handler: run single-post email job (called via non-blocking request, no user session).
68 *
69 * @since 9.0.0
70 * @return void
71 */
72 public function send_analytics_email_background() {
73 $job_key = isset( $_POST['job_key'] ) ? sanitize_text_field( wp_unslash( $_POST['job_key'] ) ) : '';
74 $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : '';
75 if ( '' === $job_key || ! wp_verify_nonce( $nonce, 'analytify_email_bg_' . $job_key ) ) {
76 wp_die( '', 403 );
77 }
78 $job = get_transient( 'analytify_email_job_' . $job_key );
79 delete_transient( 'analytify_email_job_' . $job_key );
80 if ( ! is_array( $job ) || empty( $job['post_id'] ) ) {
81 wp_die( '', 400 );
82 }
83 $this->do_send_single_analytics_email(
84 (int) $job['post_id'],
85 isset( $job['start_date'] ) ? $job['start_date'] : '',
86 isset( $job['end_date'] ) ? $job['end_date'] : '',
87 isset( $job['recipient_email'] ) ? $job['recipient_email'] : ''
88 );
89 wp_die();
90 }
91
92 /**
93 * Build and send single-post analytics email (Search Console + GA + wp_mail). Used by background job.
94 *
95 * @since 9.0.0
96 * @param int $post_id Post ID.
97 * @param string $start_date Start date (YYYY-MM-DD).
98 * @param string $end_date End date (YYYY-MM-DD).
99 * @param string $recipient_email Recipient email or empty to use saved list.
100 * @return void
101 */
102 protected function do_send_single_analytics_email( $post_id, $start_date, $end_date, $recipient_email ) {
103 $site_url = site_url();
104
105 if ( 0 === $post_id ) {
106 $u_post = '/'; // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- This is intentionally commented out
107 } else {
108 $permalink = get_permalink( (int) $post_id );
109 $u_post = $permalink ? wp_parse_url( $permalink ) : array();
110 }
111
112 if ( is_array( $u_post ) && isset( $u_post['host'] ) && 'localhost' === $u_post['host'] ) {
113 $filter = 'ga:pagePath==/'; // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- This is intentionally commented out
114 } else {
115 $filter = 'ga:pagePath==' . ( is_array( $u_post ) && isset( $u_post['path'] ) ? $u_post['path'] : '' ) . '';
116 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- This is intentionally commented out
117 // $filter = 'ga:pagePath==' . $u_post['host'] . '/';
118 $filter = apply_filters( 'analytify_page_path_filter', $filter, $u_post );
119 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- This is intentionally commented out
120 // Url have query string incase of WPML.
121 if ( is_array( $u_post ) && isset( $u_post['query'] ) ) {
122 $filter .= '?' . $u_post['query'];
123 }
124 }
125
126 if ( '' === $start_date ) {
127 $post_obj = get_post( (int) $post_id );
128 $s_date = $post_obj ? get_the_time( 'Y-m-d', $post_obj ) : gmdate( 'Y-m-d' );
129 if ( $post_obj && get_the_time( 'Y', $post_obj ) < 2005 ) {
130 $s_date = '2005-01-01';
131 }
132 } else {
133 $s_date = $start_date;
134 }
135
136 if ( '' === $end_date ) {
137 $e_date = gmdate( 'Y-m-d' );
138 } else {
139 $e_date = $end_date;
140 }
141 $search_console_stats = $this->WP_ANALYTIFY->get_search_console_stats( // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase -- Variable name is acceptable for this context
142 'post_' . $post_id,
143 array(
144 'start' => $s_date,
145 'end' => $e_date,
146 )
147 );
148 $total_clicks = 0;
149 $total_impressions = 0;
150 $total_ctrs = 0;
151 if ( isset( $search_console_stats['response']['rows'] ) && count( $search_console_stats['response']['rows'] ) > 0 ) {
152 foreach ( $search_console_stats['response']['rows'] as $row ) {
153 $total_clicks += $row['clicks'];
154 $total_impressions += $row['impressions'];
155 }
156 }
157
158 if ( $total_impressions > 0 ) {
159 $total_ctrs = round( ( $total_clicks / $total_impressions ) * 100 );
160 } else {
161 $total_ctrs = 0;
162 }
163
164 $wp_analytify = $GLOBALS['WP_ANALYTIFY'];
165
166 $_logo_id = $wp_analytify->settings->get_option( 'analytify_email_logo', 'wp-analytify-email' );
167 if ( $_logo_id ) {
168 $_logo_link_array = wp_get_attachment_image_src( $_logo_id, array( 150, 150 ) );
169 $logo_link = is_array( $_logo_link_array ) && isset( $_logo_link_array[0] ) ? $_logo_link_array[0] : '';
170 } else {
171 $logo_link = ( defined( 'ANALYTIFY_IMAGES_PATH' ) ? ANALYTIFY_IMAGES_PATH : 'https://analytify.io/assets/email/' ) . 'logo.png';
172 }
173
174 if ( $recipient_email ) {
175 $emails_array = array( $recipient_email );
176 } else {
177 $emails = $wp_analytify->settings->get_option( 'analytify_email_user_email', 'wp-analytify-email' );
178 $emails_array = array();
179 if ( ! empty( $emails ) ) {
180 if ( ! is_array( $emails ) ) {
181 $emails_array = explode( ',', $emails );
182 } else {
183 $emails_array = $emails;
184 }
185 }
186 }
187
188 $subject = 'Analytics for ' . get_the_title( (int) $post_id );
189
190 $_from_name = $wp_analytify->settings->get_option( 'analytiy_from_name', 'wp-analytify-email' );
191 $_from_name = ! empty( $_from_name ) ? $_from_name : 'Analytify Notifications';
192
193 $_from_email = $wp_analytify->settings->get_option( 'analytiy_from_email', 'wp-analytify-email' );
194 $_from_email = ! empty( $_from_email ) ? $_from_email : 'no-reply@analytify.io';
195
196 foreach ( $emails_array as $email_group ) {
197
198 if ( is_array( $email_group ) ) {
199 $email_group_name = trim( $email_group['name'] );
200 $name = ! empty( $email_group_name ) ? ' ' . esc_html( $email_group_name ) : '';
201 $email_address = sanitize_email( $email_group['email'] );
202 } else {
203 $name = '';
204 $email_address = sanitize_email( $email_group );
205 $name = ucwords( strstr( $email_address, '@', true ) ? strstr( $email_address, '@', true ) : '' );
206 }
207
208 $headers = array(
209 'From: ' . $_from_name . ' <' . $_from_email . '>',
210 // phpcs:ignore Squiz.PHP.CommentedOutCode.Found -- This is intentionally commented out
211 // 'To: '. $email_address,
212 'Content-Type: text/html; charset=UTF-8',
213 );
214 // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedStylesheet -- This is inline CSS for email templates
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 <title>Analytify</title>
221 <style type="text/css">
222 @media screen and (max-width: 620px) {
223 .main-table {
224 width: 100% !important;
225 }
226 }
227
228 @media screen and (max-width: 560px) {
229 .box-table>tbody>tr>td {
230 width: 100% !important;
231 display: block !important;
232 margin-bottom: 10px !important;
233 }
234
235 .session-table>table {
236 display: block !important;
237 width: 100% !important;
238 }
239
240 .session-table>table>tbody {
241 display: block !important;
242 width: 100% !important;
243 }
244
245 .session-table>table>tbody>tr {
246 display: block !important;
247 width: 96% !important;
248 margin: 10px 2% 10px !important;
249 }
250
251 .os-table>td,
252 .keywords-table>td {
253 display: block;
254 width: 100% !important;
255 }
256
257 .geographic-table>tbody>tr>td {
258 display: block !important;
259 width: 100% !important;
260 }
261
262 .user-data>table>tbody>tr>td {
263 padding: 10px !important;
264 }
265
266 .mobile-hide {
267 display: none !important;
268 }
269
270 .main-table>tbody>tr>td {
271 padding: 10px !important;
272 }
273
274 .user-data>table>tbody>tr>td img {
275 margin-left: 0 !important;
276 }
277 }
278
279 @media (prefers-color-scheme: dark ) {
280 body, [bgcolor="#ffffff"],[bgcolor="#f5f9ff"],[bgcolor="#f9fafa"], [bgcolor="#f3f7fa"], .session-table, .session-table tr td{
281 background-color: #000 !important;
282 }
283 table[bgcolor="#f9fafa"]>tbody>tr>td, .os-table td,.geographic-table{
284 background-color: #000000 !important;
285 }
286 .session-table tr td{
287 border-color: #fff !important;
288 color: #fff !important;
289 }
290 table tbody td [color="#313133"],
291 table tbody td [color="#383b3d"],
292 table tbody td [color="#444"],
293 table tbody td [color="#444444"],
294 table tbody td [color="#848484"],
295 table tbody td [color="#909090"]{
296 color: #fff !important;
297 }
298 table tbody td hr{
299 border-top:1px solid #fff !important;
300 }
301 }
302 </style>
303 </head>
304
305 <body style="margin: 0;padding: 0; background: #f3f7fa; " bgcolor="#f3f7fa">
306 <table cellpadding="0" cellspacing="0" border="0" width="100%" align="center" bgcolor="#f3f7fa">
307
308 <tr>
309 <td valign="top" style="padding-bottom:95px">
310 <table cellpadding="0" cellspacing="0" border="0" width="600" align="center" class="main-table">
311
312 <tr>
313 <td style="padding: 22px 35px;">
314 <table width="100%" cellpadding="0" cellspacing="0" align="center">
315 <tr>
316 <td align="left"><a href="' . $site_url . '"><img src="' . $logo_link . '" alt="analytify"/></a></td>
317 <td align="right" style="font: normal 15px \'Roboto\', Arial, Helvetica, sans-serif; line-height: 1.5;">
318 <font color="#444444">' . __( 'Analytics Report', 'wp-analytify' ) . '</font><br>
319 <font color="#848484">' . $s_date . ' - ' . $e_date . '</font><br />
320 <font color="#848484"><a href="' . esc_url( get_permalink( (int) $post_id ) ? get_permalink( (int) $post_id ) : '' ) . '">' . esc_html( get_the_title( (int) $post_id ) ? get_the_title( (int) $post_id ) : '' ) . '</a></font>
321 </td>
322 </tr>
323 </table>
324 </td>
325 </tr>
326
327 <tr>
328 <td style="padding: 0 15px;">
329 <table width="100%" cellpadding="0" cellspacing="0" align="center">
330
331 <tr>
332 <td valign="top">
333 <table width="100%" cellspacing="0" cellpadding="0" border="0" align="center" bgcolor="#ffffff">
334 <tr>
335 <td style="font: 400 18px \'Roboto slab\', Arial, Helvetica, sans-serif; padding: 25px 20px 11px 20px;">
336 <font color="#444444">Hi ' . $name . ',</font>
337 </td>
338 </tr>
339 <tr>
340 <td style="font: normal 14px \'Roboto\', Arial, Helvetica, sans-serif; padding: 0px 20px 20px 20px;">
341 <font color="#848484">Analytify helped you find out your ' . ( $total_impressions ? $total_impressions : '0' ) . ' site visits, and ' . ( $total_clicks ? $total_clicks : '0' ) . ' clicks with an average CTR of ' . ( $total_ctrs ? $total_ctrs : '0' ) . '% from ' . wp_date( 'jS F Y', strtotime( $start_date ) ? strtotime( $start_date ) : time() ) . ' to ' . wp_date( 'jS F Y', strtotime( $end_date ) ? strtotime( $end_date ) : time() ) . '.</font>
342 </td>
343 </tr>
344 </table>
345 </td>
346 </tr>';
347 $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' );
348
349 // General Stats.
350 if ( is_array( $selected_stats ) && in_array( 'show-overall-general', $selected_stats, true ) ) {
351
352 if ( class_exists( 'WPANALYTIFY_Utils' ) ) {
353 $ga_mode = WPANALYTIFY_Utils::get_ga_mode();
354 if ( 'ga4' === $ga_mode ) {
355
356 $report_obj = new Analytify_Report(
357 array(
358 'dashboard_type' => 'single_post',
359 'start_date' => $s_date,
360 'end_date' => $e_date,
361 'post_id' => $post_id,
362 )
363 );
364
365 $stats = $report_obj->get_general_stats();
366
367 if ( ! function_exists( 'pa_email_include_general' ) ) {
368 $plugin_dir = defined( 'WP_ANALYTIFY_PLUGIN_DIR' ) ? WP_ANALYTIFY_PLUGIN_DIR : dirname( dirname( __DIR__ ) );
369 include $plugin_dir . '/views/email/general-stats-single.php';
370 }
371
372 if ( function_exists( 'pa_email_include_single_general' ) ) {
373 pa_email_include_single_general( $wp_analytify, $stats['boxes'], false, false, $stats['total_time_spent'] );
374 }
375 } else {
376 $report_obj = new Analytify_Report(
377 array(
378 'dashboard_type' => 'single_post',
379 'start_date' => $s_date,
380 'end_date' => $e_date,
381 'post_id' => $post_id,
382 )
383 );
384
385 $stats = $report_obj->get_general_stats();
386
387 if ( ! function_exists( 'pa_email_include_general' ) ) {
388 $plugin_dir = defined( 'WP_ANALYTIFY_PLUGIN_DIR' ) ? WP_ANALYTIFY_PLUGIN_DIR : dirname( dirname( __DIR__ ) );
389 include $plugin_dir . '/views/email/general-stats-single.php';
390 }
391
392 if ( function_exists( 'pa_email_include_single_general' ) ) {
393 pa_email_include_single_general( $wp_analytify, $stats['boxes'], false, false, false );
394 }
395 }
396 }
397 }
398
399 $dates = array(
400 'start_date' => $start_date,
401 'end_date' => $end_date,
402 );
403
404 // Get pro settings options.
405 $message = apply_filters( 'wp_analytify_single_email', $message, $selected_stats, $dates );
406
407 $message .= ' </table>
408 </td>
409 </tr>
410 </table>
411 </td>
412 </tr>
413 </table>
414 </td>
415 </tr>
416 </table>
417 </body>
418 </html>';
419
420 wp_mail( $email_address, $subject, $message, $headers );
421 }
422 }
423 }
424