PluginProbe
ShopBuilder – WooCommerce Builder For Elementor / 3.2.6
ShopBuilder – WooCommerce Builder For Elementor v3.2.6
3.4.2 3.4.1 3.4.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.10 2.1.11 2.1.12 2.1.13 2.1.14 2.1.15 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 All 63 releases
shopbuilder / app / Modules / AbandonedCartRecovery / RecoveryEmail.php

RecoveryEmail.php in ShopBuilder – WooCommerce Builder For Elementor 3.2.6, at app/Modules/AbandonedCartRecovery/RecoveryEmail.php

409 lines 15.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sticky Abandoned Cart Recovery Module Class.
4 *
5 * @package RadiusTheme\SB
6 */
7
8 namespace RadiusTheme\SB\Modules\AbandonedCartRecovery;
9
10 use RadiusTheme\SB\Helpers\Fns;
11 use RadiusTheme\SB\Traits\SingletonTrait;
12 use WC_Order;
13
14
15 defined( 'ABSPATH' ) || exit();
16
17 /**
18 * Sticky add-to-cart Module Class.
19 */
20 class RecoveryEmail {
21 /**
22 * Singleton Trait.
23 */
24 use SingletonTrait;
25
26 /**
27 * Send email using WooCommerce mailer.
28 *
29 * @param string $to Recipient email(s), comma-separated or array.
30 * @param string $subject Email subject.
31 * @param string $message Email HTML body.
32 * @param string|null $from_name Optional from name.
33 * @param string|null $from_email Optional from email.
34 * @param string|null $reply_to Optional reply-to email.
35 * @return bool True if sent, false otherwise.
36 */
37 protected function send_wc_email( $to, $subject, $message, $from_name = null, $from_email = null, $reply_to = null ) {
38 if ( empty( $to ) || empty( $subject ) || empty( $message ) ) {
39 return false;
40 }
41 $mailer = WC()->mailer();
42 // Wrap in WooCommerce email template.
43 $wrapped_message = $mailer->wrap_message( $subject, $message );
44 // Default headers.
45 $from_name = $from_name ?? CartRecoveryFns::get_options( 'from_name', get_bloginfo( 'name' ) );
46 $from_email = $from_email ?? CartRecoveryFns::get_options( 'from_address', get_option( 'admin_email' ) );
47 $reply_to = $reply_to ?? CartRecoveryFns::get_options( 'reply_to_address', get_option( 'admin_email' ) );
48 $headers = [
49 'Content-Type: text/html; charset=UTF-8',
50 'From: ' . $from_name . ' <' . $from_email . '>',
51 'Reply-To: ' . $reply_to,
52 ];
53 return $mailer->send( $to, $subject, $wrapped_message, $headers );
54 }
55 /**
56 * @param array $email_history email send history.
57 * @return false
58 */
59 public function send_recovery_email( $email_history ) {
60 $abandonment_id = $email_history['abandonment_id'] ?? 0;
61 $template_id = $email_history['template_id'] ?? 0;
62 if ( ! ( $abandonment_id && $template_id ) ) {
63 return false;
64 }
65 $template = CartRecoveryDB::getTemplateById( $template_id );
66
67 if ( empty( $template ) ) {
68 return false;
69 }
70 $abandonment = CartRecoveryDB::getCaAbandonmentByID( $abandonment_id );
71 if ( empty( $abandonment ) ) {
72 return false;
73 }
74 if ( 'completed' === $abandonment['order_status'] ) {
75 return false;
76 }
77 $coupon_code = CartRecoveryFns::create_wc_coupon_from_template( $template, $abandonment );
78 if ( ! empty( $coupon_code ) ) {
79 $abandonment['coupon_code'] = $coupon_code;
80 CartRecoveryDB::updateCaAbandonmentById( $abandonment['id'], [ 'coupon_code' => $coupon_code ] );
81 CartRecoveryDB::update_scheduled_email_history( $email_history['id'], [ 'coupon_code' => $coupon_code ] );
82 }
83 $send = $this->send_template_to_email( $abandonment, $template, $email_history );
84 $key = 'email_send_total';
85 $email_send_count = CartRecoveryDB::get_email_template_meta( $template_id, $key, 0 );
86 $count = absint( $email_send_count ) + 1;
87 CartRecoveryDB::update_email_template_meta( $template_id, $key, $count );
88 return ! empty( $email_history['id'] );
89 }
90 /**
91 * @param array $abandonment abandonment.
92 * @param array $template Template.
93 * @param array $email_history email history.
94 * @return bool
95 */
96 public function send_template_to_email( $abandonment, $template, $email_history ) {
97 if ( empty( $template ) || empty( $abandonment ) ) {
98 return false;
99 }
100 if ( 'completed' === $abandonment['order_status'] ) {
101 return false;
102 }
103 $history_id = $email_history['id'] ?? 0;
104 $abandonment_id = $abandonment['id'];
105 $other_fields = $abandonment['other_fields'] ?? [];
106 // Generate URLs.
107 $checkout_url = Fns::generate_signed_url(
108 [
109 'restore_id' => $abandonment_id,
110 'template_id' => $template['id'],
111 'history_id' => $history_id,
112 ],
113 wc_get_checkout_url(),
114 'rtsbRestoreCart'
115 );
116 $unsubscribe_url = Fns::generate_signed_url(
117 [
118 'unsubscribe' => $abandonment_id,
119 'template_id' => $template['id'],
120 'history_id' => $history_id,
121 ],
122 get_permalink( CartRecoveryFns::get_options( 'unsubscribe_page', 0 ) ) ?: home_url(),
123 'rtsbUnsubscribe'
124 );
125 $administrator = get_users(
126 [
127 'role' => 'Administrator',
128 'number' => 1,
129 ]
130 );
131 // Admin info.
132 $admin_user = reset( $administrator );
133 $admin_first_name = $admin_user->user_firstname ?: __( 'Admin', 'shopbuilder' );
134 $admin_last_name = $admin_user->user_lastname ?? '';
135 $admin_full_name = trim( $admin_first_name . ' ' . $admin_last_name );
136
137 $replacements = [
138 '{{store.name}}' => get_bloginfo( 'name' ),
139 '{{store.url}}' => home_url(),
140 '{{cart.item_count}}' => $other_fields['item_count'] ?? '',
141 '{{customer.first_name}}' => $other_fields['first_name'] ?? '',
142 '{{customer.last_name}}' => $other_fields['last_name'] ?? '',
143 '{{customer.full_name}}' => $other_fields['full_name'] ?? '',
144 '{{admin.firstname}}' => $admin_first_name,
145 '{{admin.lastname}}' => $admin_last_name,
146 '{{admin.full_name}}' => $admin_full_name,
147 '{{cart.total}}' => $abandonment['cart_total'] ?? '',
148 '{{cart.checkout_url}}' => esc_url( $checkout_url ),
149 '{{cart.unsubscribe}}' => esc_url( $unsubscribe_url ),
150 '{{cart.coupon_code}}' => $abandonment['coupon_code'] ?? '',
151 '{{cart.abandoned_date}}' => $abandonment['time'] ?? '',
152 '{{customer.email}}' => $abandonment['email'] ?? '',
153 ];
154
155 $message = strtr( $template['email_body'], $replacements );
156
157 $tracking_url = add_query_arg(
158 [
159 'template_id' => $template['id'] ,
160 'abandonment_id' => $abandonment_id,
161 'history_id' => $history_id,
162 ],
163 home_url( '/rtsb-ca-email-open/' )
164 );
165 $message .= '<img src="' . esc_url( $tracking_url ) . '" width="1" height="1" style="display:none;">';
166
167 $to = $abandonment['email'];
168 $subject = $template['email_subject'];
169
170 return $this->send_wc_email( $to, $subject, $message );
171 }
172 /**
173 * Send a recovery notice email to the store administrator(s) with recovered products list.
174 *
175 * @param \WC_Order $order WooCommerce order object associated with the recovery notice.
176 *
177 * @return bool True on success, false on failure.
178 *
179 * @since 1.0.0
180 */
181 public function send_recovery_notice_to_admin( $order ) {
182 if ( ! $order instanceof WC_Order ) {
183 return false;
184 }
185 // Get admin emails from options.
186 $to_admin_email = CartRecoveryFns::get_options( 'recovery_admin_email', '' );
187 if ( empty( $to_admin_email ) ) {
188 return false;
189 }
190 $to_admin_email = implode( ',', array_map( 'trim', explode( ',', $to_admin_email ) ) );
191 $subject = CartRecoveryFns::get_options(
192 'recovery_notify_title',
193 /* translators: %s: Order number */
194 sprintf( esc_html__( 'Cart Recovery Success - Order #%s', 'shopbuilder' ), $order->get_order_number() )
195 );
196 // Get order items.
197 $items = $order->get_items();
198 if ( empty( $items ) ) {
199 return false;
200 }
201 // Build email message with template override support.
202 $message = $this->get_recovery_email_template( $order, $items );
203 // Send email.
204 return $this->send_wc_email( $to_admin_email, $subject, $message );
205 }
206
207 /**
208 * Get the recovery email template HTML.
209 * Supports template override via filter or custom template file.
210 *
211 * @param \WC_Order $order Order object.
212 * @param array $items Order items.
213 *
214 * @return string HTML email content.
215 */
216 private function get_recovery_email_template( $order, $items ) {
217 // Prepare template variables.
218 $store_name = get_bloginfo( 'name' );
219 $order_number = $order->get_order_number();
220 $order_total = $order->get_formatted_order_total();
221 $order_date = $order->get_date_created()->date_i18n( wc_date_format() . ' ' . wc_time_format() );
222 $customer_name = $order->get_formatted_billing_full_name();
223 $order_url = $order->get_edit_order_url();
224 // Calculate recovery statistics.
225 $recovered_count = 0;
226 $new_count = 0;
227 foreach ( $items as $item ) {
228 $restored_session = $item->get_meta( 'rtsb_restored_item', true );
229 if ( ! empty( $restored_session ) ) {
230 $recovered_count++;
231 } else {
232 $new_count++;
233 }
234 }
235 // Template variables array for easy override.
236 $template_vars = [
237 'order' => $order,
238 'items' => $items,
239 'store_name' => $store_name,
240 'order_number' => $order_number,
241 'order_total' => $order_total,
242 'order_date' => $order_date,
243 'customer_name' => $customer_name,
244 'order_url' => $order_url,
245 'recovered_count' => $recovered_count,
246 'new_count' => $new_count,
247 ];
248 $custom_html = $this->render_default_recovery_email_template( $template_vars );
249 // Allow custom template via filter.
250 return apply_filters( 'rtsb/cart_recovery/admin_email_template', $custom_html, $template_vars );
251 }
252
253 /**
254 * Render the default recovery email template.
255 *
256 * @param array $vars Template variables.
257 *
258 * @return string HTML email content.
259 */
260 private function render_default_recovery_email_template( $vars ) {
261 extract( $vars ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
262
263 ob_start();
264 ?>
265 <div style="font-family: Arial, sans-serif; max-width: 700px; margin: 0 auto; background-color: #f7f7f7; padding: 10px;">
266 <!-- Header -->
267 <div style=" padding: 20px; text-align: center; border-radius: 8px 8px 0 0;">
268 <h2 style="margin: 0; font-size: 24px;">🎉 <?php esc_html_e( 'Cart Recovery Success!', 'shopbuilder' ); ?></h2>
269 </div>
270
271 <!-- Main Content -->
272 <div style="background-color: white; padding: 15px; border-radius: 0 0 8px 8px;">
273 <p style="font-size: 16px; color: #333; margin-bottom: 20px;">
274 <?php
275 echo sprintf(
276 /* translators: %s: Store name */
277 esc_html__( 'Great news! A previously abandoned cart has been successfully recovered at %s.', 'shopbuilder' ),
278 '<strong>' . esc_html( $store_name ) . '</strong>'
279 );
280 ?>
281 </p>
282
283 <!-- Order Summary -->
284 <div style="background-color: #f9f9f9; padding: 20px; border-radius: 6px; margin-bottom: 25px;">
285 <h3 style="margin-top: 0; color: #333; font-size: 18px; border-bottom: 2px solid; padding-bottom: 10px;">
286 <?php esc_html_e( 'Order Summary', 'shopbuilder' ); ?>
287 </h3>
288 <table style="width: 100%; border-collapse: collapse;">
289 <tr>
290 <td style="padding: 8px 0; color: #666;"><strong><?php esc_html_e( 'Order Number:', 'shopbuilder' ); ?></strong></td>
291 <td style="padding: 8px 0; color: #333;">#<?php echo esc_html( $order_number ); ?></td>
292 </tr>
293 <tr>
294 <td style="padding: 8px 0; color: #666;"><strong><?php esc_html_e( 'Customer:', 'shopbuilder' ); ?></strong></td>
295 <td style="padding: 8px 0; color: #333;"><?php echo esc_html( $customer_name ); ?></td>
296 </tr>
297 <tr>
298 <td style="padding: 8px 0; color: #666;"><strong><?php esc_html_e( 'Order Date:', 'shopbuilder' ); ?></strong></td>
299 <td style="padding: 8px 0; color: #333;"><?php echo esc_html( $order_date ); ?></td>
300 </tr>
301 <tr>
302 <td style="padding: 8px 0; color: #666;"><strong><?php esc_html_e( 'Order Total:', 'shopbuilder' ); ?></strong></td>
303 <td style="padding: 8px 0; font-size: 18px; font-weight: bold;"><?php echo wp_kses_post( $order_total ); ?></td>
304 </tr>
305 </table>
306 </div>
307
308 <!-- Recovery Statistics -->
309 <div style="display: flex; gap: 15px; margin-bottom: 25px;">
310 <div style="flex: 1; background-color: #e8f5e9; padding: 15px; border-radius: 6px; text-align: center;">
311 <div style="font-size: 28px; font-weight: bold; "><?php echo absint( $recovered_count ); ?></div>
312 <div style="color: #666; font-size: 14px;"><?php esc_html_e( 'Recovered Items', 'shopbuilder' ); ?></div>
313 </div>
314 <?php if ( $new_count > 0 ) : ?>
315 <div style="flex: 1; background-color: #e3f2fd; padding: 15px; border-radius: 6px; text-align: center;">
316 <div style="font-size: 28px; font-weight: bold; color: #2196F3;"><?php echo absint( $new_count ); ?></div>
317 <div style="color: #666; font-size: 14px;"><?php esc_html_e( 'New Items', 'shopbuilder' ); ?></div>
318 </div>
319 <?php endif; ?>
320 </div>
321
322 <!-- Products Table -->
323 <h3 style="color: #333; font-size: 18px; border-bottom: 2px solid; padding-bottom: 10px; margin-bottom: 15px;">
324 <?php esc_html_e( 'Recovered Products', 'shopbuilder' ); ?>
325 </h3>
326
327 <table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">
328 <thead>
329 <tr style="background-color: #f5f5f5;">
330 <th style="padding: 12px; text-align: left; border-bottom: 2px solid #ddd; font-size: 14px; color: #666;">
331 <?php esc_html_e( 'Product', 'shopbuilder' ); ?>
332 </th>
333 <th style="padding: 12px; text-align: center; border-bottom: 2px solid #ddd; font-size: 14px; color: #666;">
334 <?php esc_html_e( 'Qty', 'shopbuilder' ); ?>
335 </th>
336 <th style="padding: 12px; text-align: center; border-bottom: 2px solid #ddd; font-size: 14px; color: #666;">
337 <?php esc_html_e( 'Status', 'shopbuilder' ); ?>
338 </th>
339 <th style="padding: 12px; text-align: right; border-bottom: 2px solid #ddd; font-size: 14px; color: #666;">
340 <?php esc_html_e( 'Total', 'shopbuilder' ); ?>
341 </th>
342 </tr>
343 </thead>
344 <tbody>
345 <?php foreach ( $items as $item ) : ?>
346 <?php
347 $restored_session = $item->get_meta( 'rtsb_restored_item', true );
348 $is_recovered = ! empty( $restored_session );
349 $product = $item->get_product();
350 $product_name = $item->get_name();
351 $quantity = $item->get_quantity();
352 $line_total = wc_price( $item->get_total() );
353 $image_url = $product ? wp_get_attachment_url( $product->get_image_id() ) : '';
354 ?>
355 <tr style="border-bottom: 1px solid #eee;">
356 <td style="padding: 12px;">
357 <div style="display: flex; align-items: center; gap: 12px;">
358 <?php if ( $image_url ) : ?>
359 <img src="<?php echo esc_url( $image_url ); ?>"
360 alt="<?php echo esc_attr( $product_name ); ?>"
361 style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px; border: 1px solid #ddd;">
362 <?php endif; ?>
363 <span style="color: #333; font-weight: 500;"><?php echo esc_html( $product_name ); ?></span>
364 </div>
365 </td>
366 <td style="padding: 12px; text-align: center; color: #666;">
367 <?php echo absint( $quantity ); ?>
368 </td>
369 <td style="padding: 12px; text-align: center;">
370 <?php if ( $is_recovered ) : ?>
371 <span style="padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: bold;"> <?php esc_html_e( 'Recovered', 'shopbuilder' ); ?> </span>
372 <?php else : ?>
373 <span style="background-color: #2196F3;padding: 4px 12px; border-radius: 12px; font-size: 12px; font-weight: bold;">
374 <?php esc_html_e( 'New', 'shopbuilder' ); ?>
375 </span>
376 <?php endif; ?>
377 </td>
378 <td style="padding: 12px; text-align: right; color: #333; font-weight: 500;">
379 <?php echo wp_kses_post( $line_total ); ?>
380 </td>
381 </tr>
382 <?php endforeach; ?>
383 </tbody>
384 </table>
385
386 <!-- View Order Button -->
387 <div style="text-align: center; margin-top: 30px;">
388 <a href="<?php echo esc_url( $order_url ); ?>" style="display: inline-block; padding: 12px 30px; text-decoration: none; border-radius: 5px; font-weight: bold; font-size: 16px;"> <?php esc_html_e( 'View Order Details', 'shopbuilder' ); ?> </a>
389 </div>
390 </div>
391
392 <!-- Footer -->
393 <div style="text-align: center; padding: 10px; color: #999; font-size: 12px;">
394 <p style="margin: 0;">
395 <?php
396 echo sprintf(
397 /* translators: %s: Store name */
398 esc_html__( 'This is an automated notification from %s', 'shopbuilder' ),
399 '<strong>' . esc_html( $store_name ) . '</strong>'
400 );
401 ?>
402 </p>
403 </div>
404 </div>
405 <?php
406 return ob_get_clean();
407 }
408 }
409