PluginProbe
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce / 3.3.0
CartFlows – Funnel Builder & Checkout Plugin for WooCommerce v3.3.0
3.3.0 3.2.1 3.2.0 3.1.4 3.1.3 3.1.2 3.1.1 3.1.0 3.0.1 trunk 1.0.4 1.1.0 1.1.0.1 1.1.1 1.1.10 1.1.11 1.1.12 1.1.13 1.1.14 1.1.15 1.1.16 1.1.17 1.1.18 1.1.19 1.1.2 All 162 releases
cartflows / modules / email-report / class-cartflows-admin-report-emails.php

class-cartflows-admin-report-emails.php in CartFlows – Funnel Builder & Checkout Plugin for WooCommerce 3.3.0, at modules/email-report/class-cartflows-admin-report-emails.php

402 lines 13.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Email.
4 *
5 * @package CartFlows.
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly.
10 }
11
12 use CartflowsAdmin\AdminCore\Inc\AdminHelper;
13 /**
14 * Class Cartflows_Admin_Report_Emails.
15 */
16 class Cartflows_Admin_Report_Emails {
17
18
19 /**
20 * Class instance.
21 *
22 * @access private
23 * @var $instance Class instance.
24 */
25 private static $instance;
26
27 /**
28 * Initiator
29 */
30 public static function get_instance() {
31 if ( ! isset( self::$instance ) ) {
32 self::$instance = new self();
33 }
34 return self::$instance;
35 }
36
37
38 /**
39 * Constructor.
40 */
41 public function __construct() {
42
43 // It will run once.
44 add_action( 'admin_init', array( $this, 'schedule_weekly_report_email' ) );
45
46 add_action( 'cartflows_send_report_summary_email', array( $this, 'send_weekly_report_email' ) );
47
48 add_filter( 'admin_init', array( $this, 'unsubscribe_cartflows_weekly_emails' ), 10 );
49 }
50
51 /**
52 * Schedule the weekly email.
53 */
54 public function schedule_weekly_report_email() {
55
56 $is_report_emails = get_option( 'cartflows_stats_report_emails', 'enable' );
57
58 if ( 'enable' === $is_report_emails && function_exists( 'as_next_scheduled_action' ) && false === as_next_scheduled_action( 'cartflows_send_report_summary_email' ) ) {
59
60 wcf()->logger->log( 'Is weekly report enabled: ' . $is_report_emails );
61
62 $date = new DateTime( 'next monday 2pm' );
63
64 // It will automatically reschedule the action once initiated.
65 as_schedule_recurring_action( $date, WEEK_IN_SECONDS, 'cartflows_send_report_summary_email' );
66
67 wcf()->logger->log( 'Weekly report action scheduled. Action: cartflows_send_report_summary_email ' );
68 } elseif ( 'enable' !== $is_report_emails && as_next_scheduled_action( 'cartflows_send_report_summary_email' ) ) {
69 as_unschedule_all_actions( 'cartflows_send_report_summary_email' );
70
71 wcf()->logger->log( 'Weekly report action unscheduled. Action: cartflows_send_report_summary_email ' );
72 }
73 }
74
75 /**
76 * Send weekly report email.
77 */
78 public function send_weekly_report_email() {
79
80 $is_report_emails = get_option( 'cartflows_stats_report_emails', 'enable' );
81
82 $emails = get_option( 'cartflows_stats_report_email_ids', get_option( 'admin_email' ) );
83
84 wcf()->logger->log( 'Start-' . __CLASS__ . '::' . __FUNCTION__ );
85
86 if ( 'enable' === $is_report_emails && ! empty( $emails ) && apply_filters( 'cartflows_send_weekly_report_email', true ) ) {
87
88 $stats = $this->get_last_week_stats();
89
90 wcf()->logger->log( 'Send weekly emails to ' . $emails );
91 wcf()->logger->log( 'Total Revenue: ' . $stats['total_revenue'] );
92
93 if ( isset( $stats['total_revenue_raw'] ) && $stats['total_revenue_raw'] > 0 ) {
94
95 $subject = $this->get_email_subject( $stats );
96 $headers = 'From: ' . get_bloginfo( 'name' ) . ' <' . get_option( 'admin_email' ) . '>' . "\r\n";
97 $headers .= "Content-Type: text/html;\r\n";
98
99 $emails = preg_split( "/[\f\r\n]+/", $emails );
100
101 foreach ( $emails as $email_id ) {
102 $user_info = get_user_by( 'email', $email_id );
103 $name = $user_info ? $user_info->display_name : __( 'There', 'cartflows' );
104 $email_body = $this->get_email_content( $stats, $name, $email_id );
105 $status = wp_mail( $email_id, $subject, stripslashes( $email_body ), $headers ); //phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_mail_wp_mail
106 wcf()->logger->log( 'Email send status for email ' . $email_id . ' is ' . $status );
107 }
108 }
109 }
110 }
111
112 /**
113 * Unsubscribe the user from the mailing list.
114 */
115 public function unsubscribe_cartflows_weekly_emails() {
116
117 $unsubscribe = filter_input( INPUT_GET, 'unsubscribe_weekly_email', FILTER_VALIDATE_BOOLEAN );
118 $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
119 $email = filter_input( INPUT_GET, 'email', FILTER_SANITIZE_EMAIL );
120 $token = isset( $_GET['token'] ) ? sanitize_text_field( wp_unslash( $_GET['token'] ) ) : ''; //phpcs:ignore WordPress.Security.NonceVerification.Recommended
121
122 if ( $unsubscribe && 'cartflows' === $page && ! empty( $email ) && is_user_logged_in() && current_user_can( 'cartflows_manage_settings' ) ) {
123
124 // Verify unsubscribe token to prevent CSRF.
125 $expected_token = wp_hash( 'cartflows_unsubscribe_' . $email );
126 if ( ! hash_equals( $expected_token, $token ) ) {
127 wp_die( esc_html__( 'Invalid unsubscribe link.', 'cartflows' ), esc_html__( 'Error', 'cartflows' ), array( 'response' => 403 ) );
128 }
129
130 $email_list = get_option( 'cartflows_stats_report_email_ids', false );
131
132 if ( ! empty( $email_list ) ) {
133 $email_list = preg_split( "/[\f\r\n]+/", $email_list );
134
135 $email_list = array_filter(
136 $email_list,
137 function ( $e ) use ( $email ) {
138 return ( $e !== $email );
139 }
140 );
141
142 $email_list = implode( "\n", $email_list );
143
144 update_option( 'cartflows_stats_report_email_ids', $email_list );
145 wcf()->logger->log( 'Email unsubscribed: ' . $email );
146 }
147
148 wp_die( esc_html__( 'You have successfully unsubscribed from our weekly emails list.', 'cartflows' ), esc_html__( 'Unsubscribed', 'cartflows' ) );
149 }
150 }
151
152 /**
153 * Get the stats mention in to email.
154 */
155 public function get_last_week_stats() {
156
157 $start_date = gmdate( 'Y-m-d', strtotime( '-7 days' ) );
158 $end_date = gmdate( 'Y-m-d' );
159
160 return AdminHelper::get_earnings( $start_date, $end_date );
161 }
162
163 /**
164 * Get the stats mention in to email.
165 */
166 public function get_last_month_stats() {
167
168 $start_date = gmdate( 'Y-m-d', strtotime( '-30 days' ) );
169 $end_date = gmdate( 'Y-m-d' );
170
171 return AdminHelper::get_earnings( $start_date, $end_date );
172 }
173
174 /**
175 * Get admin report email subject.
176 *
177 * @param array<string, mixed> $stats Weekly stats for revenue interpolation.
178 * @return string
179 */
180 public function get_email_subject( $stats = array() ) {
181
182 // Strip wc_price() HTML wrappers and decode numeric entities (e.g. &#8360; for ₨) so currency symbols render in plain-text subject lines.
183 $revenue = isset( $stats['total_revenue'] ) && is_string( $stats['total_revenue'] )
184 ? html_entity_decode( wp_strip_all_tags( $stats['total_revenue'] ), ENT_QUOTES, 'UTF-8' )
185 : '';
186
187 if ( '' === $revenue ) {
188 return esc_html__( 'Your weekly CartFlows report', 'cartflows' );
189 }
190
191 /* translators: %s: Total revenue earned in the last 7 days, formatted with currency. */
192 return sprintf( esc_html__( 'Your weekly CartFlows report — %s earned', 'cartflows' ), $revenue );
193 }
194
195 /**
196 * Get a weekly tip for the "One thing to try this week" section.
197 * Rotates by ISO week so the same tip shows for all sends in the same week.
198 *
199 * @since 3.1.2
200 * @param array<string, mixed> $stats Weekly stats (allows the filter to choose data-driven tips).
201 * @return array{headline: string, body: string}
202 */
203 public function get_weekly_tip( $stats = array() ) {
204
205 $tips = array(
206 array(
207 'headline' => __( 'Add a one-click upsell to your top flow.', 'cartflows' ),
208 'body' => __( 'Stores that add one see an average +12% lift in average order value.', 'cartflows' ),
209 ),
210 array(
211 'headline' => __( 'Enable an order bump on your highest-traffic checkout.', 'cartflows' ),
212 'body' => __( 'Order bumps consistently lift revenue per order with minimal effort.', 'cartflows' ),
213 ),
214 array(
215 'headline' => __( 'Run an A/B test on your top-performing flow.', 'cartflows' ),
216 'body' => __( 'Test a single change — headline, button, or image — and let the winner run.', 'cartflows' ),
217 ),
218 array(
219 'headline' => __( 'Add a thank-you page upsell.', 'cartflows' ),
220 'body' => __( 'Post-purchase offers convert better than pre-purchase upsells for many stores.', 'cartflows' ),
221 ),
222 array(
223 'headline' => __( 'Streamline your checkout to a single column.', 'cartflows' ),
224 'body' => __( 'Single-column checkouts reduce cognitive load and tend to convert better on mobile.', 'cartflows' ),
225 ),
226 array(
227 'headline' => __( 'Send abandoned cart recovery emails.', 'cartflows' ),
228 'body' => __( 'Most stores recover 8–15% of abandoned carts with a simple two-email sequence.', 'cartflows' ),
229 ),
230 array(
231 'headline' => __( 'Use a countdown timer on limited-time offers.', 'cartflows' ),
232 'body' => __( 'Urgency, when honest, lifts conversion on time-sensitive promotions.', 'cartflows' ),
233 ),
234 array(
235 'headline' => __( 'Pre-fill checkout fields whenever possible.', 'cartflows' ),
236 'body' => __( 'Each saved keystroke reduces friction at the highest-stakes step of the journey.', 'cartflows' ),
237 ),
238 );
239
240 $index = (int) gmdate( 'W' ) % count( $tips );
241 $tip = $tips[ $index ];
242
243 /**
244 * Filter the weekly tip shown in the report email.
245 *
246 * @param array<string, mixed> $tip Default tip with 'headline' and 'body'.
247 * @param array<string, mixed> $stats Weekly stats.
248 */
249 $filtered = apply_filters( 'cartflows_weekly_email_tip', $tip, $stats );
250
251 if (
252 is_array( $filtered )
253 && isset( $filtered['headline'], $filtered['body'] )
254 && is_string( $filtered['headline'] )
255 && is_string( $filtered['body'] )
256 ) {
257 return array(
258 'headline' => $filtered['headline'],
259 'body' => $filtered['body'],
260 );
261 }
262
263 return $tip;
264 }
265
266 /**
267 * Get top wins data (top flow, best step, top bump) for the highlights section.
268 * Free returns an empty array; Pro hooks the filter to return real data.
269 *
270 * @since 3.1.2
271 * @param string $start_date Range start (Y-m-d).
272 * @param string $end_date Range end (Y-m-d).
273 * @return array<int, array{label?: string, detail?: string}>
274 */
275 public function get_top_wins( $start_date, $end_date ) {
276
277 /**
278 * Filter top wins bullets shown in the weekly email.
279 *
280 * @param array<int, mixed> $bullets Default empty array.
281 * @param string $start_date Range start.
282 * @param string $end_date Range end.
283 */
284 $bullets = apply_filters( 'cartflows_weekly_email_top_wins', array(), $start_date, $end_date );
285
286 if ( ! is_array( $bullets ) ) {
287 return array();
288 }
289
290 $normalised = array();
291 foreach ( $bullets as $bullet ) {
292 if (
293 is_array( $bullet )
294 && isset( $bullet['label'], $bullet['detail'] )
295 && is_string( $bullet['label'] )
296 && is_string( $bullet['detail'] )
297 ) {
298 $normalised[] = array(
299 'label' => $bullet['label'],
300 'detail' => $bullet['detail'],
301 );
302 }
303 }
304
305 return $normalised;
306 }
307
308 /**
309 * Get quick link destinations for the report email.
310 *
311 * @since 3.1.2
312 * @return array<string, string>
313 */
314 public function get_quick_links() {
315
316 $links = array(
317 __( 'View full report', 'cartflows' ) => admin_url( 'admin.php?page=cartflows' ),
318 __( 'Edit your flows', 'cartflows' ) => admin_url( 'admin.php?page=cartflows&path=flows' ),
319 __( 'Browse templates', 'cartflows' ) => admin_url( 'admin.php?page=cartflows&path=library' ),
320 __( 'Get help', 'cartflows' ) => 'https://cartflows.com/docs/',
321 );
322
323 if ( ! _is_cartflows_pro() ) {
324 $links[ __( 'Try CartFlows Pro', 'cartflows' ) ] = 'https://cartflows.com/pricing/?utm_source=newsletter&utm_medium=weekly-report-email&utm_campaign=cartflows-pro';
325 } else {
326 $links[ __( 'Discover More Products', 'cartflows' ) ] = 'https://cartflows.com/products-to-scale-growth/?utm_source=newsletter&utm_medium=weekly-report-email&utm_campaign=cartflows-bsf';
327 }
328
329 /**
330 * Filter the quick links shown in the weekly email.
331 *
332 * @param array<string, mixed> $links Map of label => URL.
333 */
334 $filtered = apply_filters( 'cartflows_weekly_email_quick_links', $links );
335
336 if ( ! is_array( $filtered ) ) {
337 return $links;
338 }
339
340 $normalised = array();
341 foreach ( $filtered as $label => $url ) {
342 if ( is_string( $label ) && is_string( $url ) ) {
343 $normalised[ $label ] = $url;
344 }
345 }
346
347 return $normalised;
348 }
349
350 /**
351 * Get admin report email content.
352 *
353 * @param array $stats reports details.
354 * @param string $user_name user name.
355 * @param string $email_id email id.
356 */
357 public function get_email_content( $stats, $user_name, $email_id ) {
358
359 $cf_logo = CARTFLOWS_URL . 'assets/images/cartflows-email-logo.png';
360 $unsubscribe_token = wp_hash( 'cartflows_unsubscribe_' . $email_id );
361 $unsubscribe_link = add_query_arg(
362 array(
363 'page' => 'cartflows',
364 'unsubscribe_weekly_email' => true,
365 'email' => $email_id,
366 'token' => $unsubscribe_token,
367 ),
368 admin_url( 'admin.php' )
369 );
370 $facebook_icon = CARTFLOWS_URL . 'assets/images/facebook2x.png';
371 $twitter_icon = CARTFLOWS_URL . 'assets/images/twitter2x.png';
372 $youtube_icon = CARTFLOWS_URL . 'assets/images/youtube2x.png';
373 $from_date = gmdate( 'M j', strtotime( '-7 days' ) );
374 $to_date = gmdate( 'M j' );
375 $total_orders = $stats['total_orders'];
376 $total_visits = $stats['total_visits'];
377 $order_bump_revenue = $stats['total_bump_revenue'];
378 $offers_revenue = $stats['total_offers_revenue'];
379 $lock_icon = CARTFLOWS_URL . 'assets/images/lock.png';
380
381 $total_revenue = $stats['total_revenue'];
382 $last_month_stats = $this->get_last_month_stats();
383 $last_month_revenue = $last_month_stats['total_revenue'];
384 $store_name = get_bloginfo( 'name' );
385
386 $is_pro = _is_cartflows_pro();
387 $start_date = gmdate( 'Y-m-d', strtotime( '-7 days' ) );
388 $end_date = gmdate( 'Y-m-d' );
389 $top_wins = $this->get_top_wins( $start_date, $end_date );
390 $weekly_tip = $this->get_weekly_tip( $stats );
391 $quick_links = $this->get_quick_links();
392 $sign_off = __( 'The CartFlows team', 'cartflows' );
393
394 /* translators: 1: total orders, 2: total visits. */
395 $preheader = sprintf( esc_html__( '%1$s orders from %2$s visits — here\'s what to optimize next.', 'cartflows' ), (int) $total_orders, (int) $total_visits );
396
397 return include CARTFLOWS_DIR . 'modules/email-report/templates/email-body.php';
398 }
399 }
400
401 Cartflows_Admin_Report_Emails::get_instance();
402