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 / CartRecoveryFns.php

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

361 lines 11.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Sticky add-to-cart Functions Class.
4 *
5 * @package Rse\SB
6 */
7
8 namespace RadiusTheme\SB\Modules\AbandonedCartRecovery;
9
10 use DateInterval;
11 use DatePeriod;
12 use DateTime;
13 use RadiusTheme\SB\Helpers\Fns;
14
15 defined( 'ABSPATH' ) || exit();
16
17 /**
18 * Sticky add-to-cart Functions Class.
19 */
20 class CartRecoveryFns {
21 /**
22 * Database table name for storing abandoned cart data.
23 *
24 * @var string
25 */
26 public static $ca_abandonment = 'rtsb_ca_cart_abandonment';
27 /**
28 * Database table name for storing meta information for each email template.
29 *
30 * @var string
31 */
32 public static $ca_abandonment_meta = 'rtsb_ca_cart_abandonment_meta';
33
34 /**
35 * Database table name for storing email templates related to cart recovery.
36 *
37 * @var string
38 */
39 public static $ca_email = 'rtsb_ca_email_templates';
40
41 /**
42 * Database table name for storing meta information for each email template.
43 *
44 * @var string
45 */
46 public static $ca_email_meta = 'rtsb_ca_email_templates_meta';
47
48 /**
49 * Database table name for storing email history, including sent status and schedule.
50 *
51 * @var string
52 */
53 public static $ca_email_history = 'rtsb_ca_email_history';
54 /**
55 * @param string $key Default Attribute.
56 * @param array|string $default Default.
57 * @return array|string
58 */
59 public static function get_options( $key = null, $default = '' ) {
60 $options = Fns::get_options( 'modules', 'abandoned_cart_recovery' );
61 if ( $key ) {
62 if ( isset( $options[ $key ] ) ) {
63 return $options[ $key ];
64 } else {
65 return $default;
66 }
67 }
68 return $options;
69 }
70 /**
71 * @param int $abandonment_id abandonment id.
72 * @return void
73 */
74 public static function unsebscribe( $abandonment_id ) {
75 CartRecoveryDB::updateCaAbandonmentById( $abandonment_id, [ 'unsubscribed' => 1 ] );
76 CartRecoveryDB::delete_unnecessery_email_history_for_abandonment( $abandonment_id );
77 }
78 /**
79 * Sanitize abandonment data before DB insert/update.
80 *
81 * @param array $data Raw data.
82 * @return array Sanitized data.
83 * @since 1.0.0
84 */
85 public static function sanitize_abandonment_data( $data = [] ) {
86 if ( ! is_array( $data ) ) {
87 return [];
88 }
89 $sanitized = [];
90 unset( $data['meta_data'], $data['abandonment_id'] );
91 foreach ( $data as $key => $value ) {
92 switch ( $key ) {
93 case 'email':
94 $sanitized[ $key ] = sanitize_email( $value );
95 break;
96 case 'cart_total':
97 $sanitized[ $key ] = html_entity_decode( wp_strip_all_tags( wc_price( $value ) ) );
98 break;
99 case 'checkout_id':
100 $sanitized[ $key ] = absint( $value );
101 break;
102 case 'cart_contents':
103 case 'other_fields':
104 $sanitized[ $key ] = maybe_serialize( $value );
105 break;
106 case 'unsubscribed':
107 $sanitized[ $key ] = intval( $value );
108 break;
109 case 'time':
110 $sanitized['time'] = gmdate( 'Y-m-d H:i:s', Fns::currentTimestampUTC() );
111 break;
112 default:
113 $sanitized[ $key ] = sanitize_text_field( $value );
114 break;
115 }
116 }
117 return $sanitized;
118 }
119 /**
120 * Create WooCommerce coupon from template data
121 *
122 * @param array $template Template data with coupon meta fields.
123 * @param array $abandonment abandonment.
124 * @return string CouponCode.
125 */
126 public static function create_wc_coupon_from_template( $template, $abandonment ) {
127 if ( empty( $template['coupon_enabled'] ) || 'on' !== $template['coupon_enabled'] ) {
128 return '';
129 }
130
131 // Generate coupon code.
132 $prefix = ! empty( $template['coupon_prefix'] ) ? $template['coupon_prefix'] : '';
133 $random_code = wp_generate_password( 6, false );
134 $coupon_code = strtoupper( sanitize_title( $prefix . $random_code ) );
135 // Create coupon post.
136 $coupon = [
137 'post_title' => $coupon_code,
138 'post_content' => '',
139 'post_status' => 'publish',
140 'post_author' => get_current_user_id(),
141 'post_type' => 'shop_coupon',
142 ];
143
144 $coupon_id = wp_insert_post( $coupon );
145
146 if ( is_wp_error( $coupon_id ) ) {
147 return $coupon_code;
148 }
149 // Set coupon meta.
150 update_post_meta( $coupon_id, 'discount_type', $template['coupon_discount_type'] ?? 'fixed' ); // or 'percentage''.
151 update_post_meta( $coupon_id, 'coupon_amount', $template['coupon_amount'] ?? 0 );
152 update_post_meta( $coupon_id, 'individual_use', 'on' === $template['coupon_individual_use'] ? 'yes' : 'no' );
153 update_post_meta( $coupon_id, 'free_shipping', 'on' === $template['coupon_free_shipping'] ? 'yes' : 'no' );
154
155 // �
156 Add usage limits
157 update_post_meta( $coupon_id, 'usage_limit', 1 ); // Limit per coupon.
158 update_post_meta( $coupon_id, 'usage_limit_per_user', 1 ); // Limit per user.
159 // �
160 Allowed emails (comma-separated or single).
161 if ( ! empty( $abandonment['email'] ) ) {
162 $emails = array_map( 'trim', explode( ',', $abandonment['email'] ) );
163 update_post_meta( $coupon_id, 'customer_email', $emails );
164 }
165
166 $abandoned_cart = maybe_unserialize( $abandonment['cart_contents'] ?? [] );
167 // �
168 Restrict coupon to selected products.
169 if ( ! empty( $abandoned_cart ) ) {
170 $products = [];
171 foreach ( $abandoned_cart as $cart_item ) {
172 $products[] = absint( $cart_item['product_id'] );
173 }
174 update_post_meta( $coupon_id, 'product_ids', $products );
175 }
176 // email.
177 update_post_meta( $coupon_id, 'coupon_created_by_ca_abandonment', 'yes' );
178 update_post_meta( $coupon_id, 'coupon_created_abandonment_id', $abandonment['id'] );
179
180 // Expiry date: current time + days.
181 if ( ! empty( $template['coupon_expiration'] ) ) {
182 $expiry = strtotime( '+' . absint( $template['coupon_expiration'] ) . ' days' );
183 update_post_meta( $coupon_id, 'date_expires', $expiry );
184 }
185 // Auto apply flag (custom, if your system supports it).
186 if ( ! empty( $template['coupon_auto_apply'] ) && 'on' === $template['coupon_auto_apply'] ) {
187 update_post_meta( $coupon_id, '_auto_apply', 'yes' );
188 }
189 return $coupon_code;
190 }
191 /**
192 * Check if a given time is between start and end dates.
193 *
194 * @param string $time The time to check (e.g., '2025-10-13 09:55:56').
195 * @param string $start Start date (e.g., '2025-10-01').
196 * @param string $end End date (e.g., '2025-10-14').
197 *
198 * @return bool True if $time is within the range, false otherwise.
199 */
200 public static function is_time_between( $time, $start, $end ) {
201 $t = strtotime( $time );
202 return ( $t >= strtotime( $start ) && $t <= strtotime( $end ) );
203 }
204 /**
205 * Count total items within a date range using cached meta lookups.
206 *
207 * @param array $items List of abandonment items (each with 'id' and 'scheduled_time').
208 * @param string $start Start date (Y-m-d H:i:s).
209 * @param string $end End date (Y-m-d H:i:s).
210 *
211 * @return int Total count of items within the given range.
212 */
213 public static function count_recoverable_items( $items, $start, $end ) {
214 static $meta_cache = [];
215 $startTime = strtotime( $start );
216 $endTime = strtotime( $end );
217 $count = 0;
218 foreach ( $items as $row ) {
219 if ( empty( $row['scheduled_times'] ) ) {
220 continue;
221 }
222 // Use static cache for meta lookups.
223 if ( ! isset( $meta_cache[ $row['id'] ]['completed_time'] ) ) {
224 $meta_cache[ $row['id'] ]['completed_time'] = CartRecoveryDB::get_abandonment_meta( $row['id'], 'completed_time' );
225 }
226 if ( ! isset( $meta_cache[ $row['id'] ]['lost_time'] ) ) {
227 $meta_cache[ $row['id'] ]['lost_time'] = CartRecoveryDB::get_abandonment_meta( $row['id'], 'lost_time' );
228 }
229 $completed_time = $meta_cache[ $row['id'] ]['completed_time'] ? strtotime( $meta_cache[ $row['id'] ]['completed_time'] ) : false;
230 $lost_time = $meta_cache[ $row['id'] ]['lost_time'] ? strtotime( $meta_cache[ $row['id'] ]['lost_time'] ) : false;
231 // Skip if completed before end time.
232 if ( $completed_time && $completed_time < $endTime ) {
233 continue;
234 }
235 // Skip if lost before end time.
236 if ( $lost_time && $lost_time < $endTime ) {
237 continue;
238 }
239 $times = array_values( $row['scheduled_times'] );
240 foreach ( $times as $time ) {
241 if ( $time >= $startTime && $time <= $endTime ) {
242 $count++;
243 break;
244 }
245 }
246 }
247 return $count;
248 }
249 /**
250 * Build chart data for recoverable and recovered orders within a date range.
251 *
252 * @param array $recoverableOrderMeta Recoverable order meta data (with 'time' key).
253 * @param array $recoveredOrderMeta Recovered order meta data (with 'meta_value' key).
254 * @param string $start Start date (Y-m-d H:i:s).
255 * @param string $end End date (Y-m-d H:i:s).
256 *
257 * @return array Formatted chart data with date, recoverable, and recovered counts.
258 */
259 public static function get_chart_data( $recoverableOrderMeta, $recoveredOrderMeta, $start, $end ) {
260 $chartData = [];
261 // Generate all dates between start and end.
262 $period = new DatePeriod(
263 new DateTime( $start ),
264 new DateInterval( 'P1D' ),
265 ( new DateTime( $end ) )
266 );
267 $startFormatted = gmdate( 'M d Y H:i:s', strtotime( $start ) );
268 foreach ( $period as $dateObj ) {
269 $date = $dateObj->format( 'M d Y' );
270 $endDateFormatted = $dateObj->format( 'M d Y 23:59:59' );
271 $count = self::count_recoverable_items( $recoverableOrderMeta, $startFormatted, $endDateFormatted );
272 $chartData[ $date ] = [
273 'date' => $date,
274 'recoverable' => $count,
275 'recovered' => 0,
276 ];
277 }
278 // Count recovered per date.
279 foreach ( $recoveredOrderMeta as $row ) {
280 if ( empty( $row['meta_value'] ) ) {
281 continue;
282 }
283 $date = gmdate( 'M d Y', strtotime( $row['meta_value'] ) );
284 if ( isset( $chartData[ $date ] ) ) {
285 $chartData[ $date ]['recovered']++;
286 }
287 }
288 // Sort and return.
289 uksort( $chartData, fn( $a, $b ) => strtotime( $a ) <=> strtotime( $b ) );
290 return array_values( $chartData );
291 }
292
293 /**
294 * Calculate scheduled timestamp based on frequency.
295 *
296 * @param int $currentTime Current timestamp.
297 * @param int $frequency Frequency value.
298 * @param string $frequency_unit Unit (MINUTE, HOUR, DAY).
299 *
300 * @return int Scheduled timestamp or 0.
301 */
302 public static function calculate_schedule_timestamp( $currentTime, $frequency, $frequency_unit ) {
303 $frequency = absint( $frequency );
304 switch ( $frequency_unit ) {
305 case 'MINUTE':
306 return $currentTime + ( $frequency * MINUTE_IN_SECONDS );
307 case 'HOUR':
308 return $currentTime + ( $frequency * HOUR_IN_SECONDS );
309 case 'DAY':
310 return $currentTime + ( $frequency * DAY_IN_SECONDS );
311 default:
312 return 0;
313 }
314 }
315 /**
316 * Prepare email history data for templates.
317 *
318 * @param array $templates Email templates.
319 * @param int $abandonment_id Abandonment ID.
320 * @param int|null $currentTime Current timestamp.
321 *
322 * @return array History data and max scheduled time.
323 */
324 public static function prepare_email_history_data( $templates, $abandonment_id, $currentTime = null ) {
325 if ( empty( $templates ) ) {
326 return [
327 'history' => [],
328 'max_scheduled' => null,
329 ];
330 }
331 $currentTime = $currentTime ?? Fns::currentTimestampUTC();
332 $historyData = [];
333 $maxScheduled = null;
334 foreach ( $templates as $template ) {
335 $scheduleTimestamp = self::calculate_schedule_timestamp(
336 $currentTime,
337 $template['frequency'],
338 $template['frequency_unit']
339 );
340 if ( ! $scheduleTimestamp ) {
341 continue;
342 }
343 $scheduleFormatted = gmdate( 'Y-m-d H:i:s', $scheduleTimestamp );
344 $historyData[] = [
345 'template_id' => $template['id'],
346 'abandonment_id' => $abandonment_id,
347 'coupon_code' => '',
348 'scheduled_time' => $scheduleFormatted,
349 'email_sent' => 0,
350 ];
351 if ( ! $maxScheduled || $scheduleTimestamp > $maxScheduled ) {
352 $maxScheduled = $scheduleTimestamp;
353 }
354 }
355 return [
356 'history' => $historyData,
357 'max_scheduled' => $maxScheduled,
358 ];
359 }
360 }
361