class-wcs-repair-2-0.php
30.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
<?php
/**
* Repair subscriptions data to v2.0
*
* @author Prospress
* @category Admin
* @package WooCommerce Subscriptions/Admin/Upgrades
* @version 2.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class WCS_Repair_2_0 {
/**
* Takes care of undefine notices in the upgrade process
*
* @param array $order_item item meta
* @return array repaired item meta
*/
public static function maybe_repair_order_item( $order_item ) {
foreach ( array( 'qty', 'tax_class', 'product_id', 'variation_id', 'recurring_line_subtotal', 'recurring_line_total', 'recurring_line_subtotal_tax', 'recurring_line_tax' ) as $key ) {
if ( ! array_key_exists( $key, $order_item ) ) {
$order_item[ $key ] = '';
}
}
return $order_item;
}
/**
* Does sanity check on every subscription, and repairs them as needed
*
* @param array $subscription subscription data to be upgraded
* @param integer $item_id id of order item meta
* @return array a repaired subscription array
*/
public static function maybe_repair_subscription( $subscription, $item_id ) {
global $wpdb;
$item_meta = get_metadata( 'order_item', $item_id );
foreach ( self::integrity_check( $subscription ) as $function ) {
$subscription = call_user_func( 'WCS_Repair_2_0::repair_' . $function, $subscription, $item_id, $item_meta );
}
return $subscription;
}
/**
* Checks for missing data on a subscription
*
* @param array $subscription data about the subscription
* @return array a list of repair functions to run on the subscription
*/
public static function integrity_check( $subscription ) {
$repairs_needed = array();
foreach ( array(
'order_id',
'product_id',
'variation_id',
'subscription_key',
'status',
'period',
'interval',
'length',
'start_date',
'trial_expiry_date',
'expiry_date',
'end_date',
) as $meta ) {
if ( ! array_key_exists( $meta, $subscription ) || '' === $subscription[ $meta ] ) {
$repairs_needed[] = $meta;
}
}
return $repairs_needed;
}
/**
* 'order_id': a subscription can exist without an original order in v2.0, so technically the order ID is no longer required.
* However, if some or all order item meta data that constitutes a subscription exists without a corresponding parent order,
* we can deem the issue to be that the subscription meta data was not deleted, not that the subscription should exist. Meta
* data could be orphaned in v1.n if the order row in the wp_posts table was deleted directly in the database, or the
* subscription/order were for a customer that was deleted in WordPress administration interface prior to Subscriptions v1.3.8.
* In both cases, the subscription, including meta data, should have been permanently deleted. However, deleting data is not a
* good idea during an upgrade. So I propose instead that we create a subscription without a parent order, but move it to the trash.
*
* Additional idea was to check whether the given order_id exists, but since that's another database read, it would slow down a lot of things.
*
* A subscription will not make it to this point if it doesn't have an order id, so this function will practically never be run
*
* @param array $subscription data about the subscription
* @return array repaired data about the subscription
*/
public static function repair_order_id( $subscription ) {
WCS_Upgrade_Logger::add( '-- Repairing order_id for subscription that is missing order id: Status changed to trash' );
WCS_Upgrade_Logger::add( '-- Shop owner: please review new trashed subscriptions. There is at least one with missing order id.' );
$subscription['status'] = 'trash';
return $subscription;
}
/**
* Combined functionality for the following functions:
* - repair_product_id
* - repair_variation_id
* - repair_recurring_line_total
* - repair_recurring_line_tax
* - repair_recurring_line_subtotal
* - repair_recurring_line_subtotal_tax
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing the id for
* @param array $item_meta meta data about the product
* @param string $item_meta_key the meta key for the data on the item meta
* @param string $subscription_meta_key the meta key for the data on the subscription
* @return array repaired data about the subscription
*/
public static function repair_from_item_meta( array $subscription, $item_id, $item_meta, $subscription_meta_key = null, $item_meta_key = null, $default_value = '' ) {
if ( ! is_array( $subscription ) || ! is_numeric( $item_id ) || ! is_array( $item_meta ) || ! is_string( $subscription_meta_key ) || ! is_string( $item_meta_key ) || ( ! is_string( $default_value ) && ! is_numeric( $default_value ) ) ) {
return $subscription;
}
if ( array_key_exists( $item_meta_key, $item_meta ) && ! empty( $item_meta[ $item_meta_key ] ) ) {
// only do the copy if the value on item meta is actually different to what the subscription has
// otherwise it'd be an extra line in the log file for no actual use
if ( ! array_key_exists( $subscription_meta_key, $subscription ) || $item_meta[ $item_meta_key ][0] != $subscription[ $subscription_meta_key ] ) {
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: copying %s from item_meta to %s on subscription.', $subscription['order_id'], $item_meta_key, $subscription_meta_key ) );
$subscription[ $subscription_meta_key ] = $item_meta[ $item_meta_key ][0];
}
} elseif ( ! array_key_exists( $item_meta_key, $item_meta ) ) {
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: setting an empty %s on old subscription, item meta was not helpful.', $subscription['order_id'], $subscription_meta_key ) );
$subscription[ $subscription_meta_key ] = $default_value;
}
return $subscription;
}
/**
* '_product_id': the only way to derive a order item's product ID would be to match the order item's name to a product name/title.
* This is quite hacky, so we may be better copying the empty product ID to the new subscription. A subscription to a deleted
* produced should be able to exist.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing the id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_product_id( $subscription, $item_id, $item_meta ) {
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'product_id', '_product_id' );
}
/**
* '_variation_id': the only way to derive a order item's product ID would be to match the order item's name to a product name/title.
* This is quite hacky, so we may be better copying the empty product ID to the new subscription. A subscription to a deleted produced
* should be able to exist.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_variation_id( $subscription, $item_id, $item_meta ) {
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'variation_id', '_variation_id' );
}
/**
* If the subscription does not have a subscription key for whatever reason (probably becuase the product_id was missing), then this one
* fills in the blank.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_subscription_key( $subscription, $item_id, $item_meta ) {
if ( ! is_numeric( $item_id ) ) {
// because item_id can be either product id or variation id, we can't use
// item meta to backfill this
$subscription['subscription_key'] = '';
} else {
$subscription['subscription_key'] = $subscription['order_id'] . '_' . $item_id;
}
return $subscription;
}
/**
* '_subscription_status': we could default to cancelled (and then potentially trash) if no status exists because the cancelled status
* is irreversible. But we can also take this a step further. If the subscription has a '_subscription_expiry_date' value and a
* '_subscription_end_date' value, and they are within a few minutes of each other, we can assume the subscription's status should be
* expired. If there is a '_subscription_end_date' value that is different to the '_subscription_expiry_date' value (either because the
* expiration value is 0 or some other date), then we can assume the status should be cancelled). If there is no end date value, we're
* a bit lost as technically the subscription hasn't ended, but we should make sure it is not active, so cancelled is still the best
* default.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_status( $subscription, $item_id, $item_meta ) {
// only reset this if we didn't repair the order_id
if ( ! array_key_exists( 'order_id', $subscription ) || empty( $subscription['order_id'] ) ) {
WCS_Upgrade_Logger::add( '-- Tried to repair status. Previously set it to trash with order_id missing, bailing.' );
return $subscription;
}
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: repairing status for subscription.', $subscription['order_id'] ) );
// if expiry_date and end_date are within 4 minutes (arbitrary), let it be expired
if ( array_key_exists( 'expiry_date', $subscription ) && ! empty( $subscription['expiry_date'] ) && array_key_exists( 'end_date', $subscription ) && ! empty( $subscription['end_date'] ) && ( 4 * MINUTE_IN_SECONDS ) >= self::time_diff( $subscription['expiry_date'], $subscription['end_date'] ) ) {
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: there are end dates and expiry dates, they are close to each other, setting status to "expired" and returning.', $subscription['order_id'] ) );
$subscription['status'] = 'expired';
} else {
// default to cancelled
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: setting the default to "cancelled".', $subscription['order_id'] ) );
$subscription['status'] = 'cancelled';
}
self::log_store_owner_review( $subscription );
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: returning the status with %s', $subscription['order_id'], $subscription['status'] ) );
return $subscription;
}
/**
* '_subscription_period': we can attempt to derive this from the time between renewal orders. For example, if there are two renewal
* orders found 3 months apart, the billing period would be month. If there are not two or more renewal orders (we can't use a single
* renewal order because that would account for the free trial) and a _product_id value , if the product still exists, we can use the
* current value set on that product. It won't always be correct, but it's the closest we can get to an accurate estimate.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_period( $subscription, $item_id, $item_meta ) {
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: repairing period for subscription', $subscription['order_id'] ) );
// Get info from the product
$subscription = self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'period', '_subscription_period', '' );
if ( '' !== $subscription['period'] ) {
return $subscription;
}
// let's get the renewal orders
$renewal_orders = self::get_renewal_orders( $subscription );
if ( count( $renewal_orders ) < 2 ) {
// default to month. Because we're defaulting, we also need to cancel this to avoid charging customers on a schedule they didn't
// agree to.
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: setting default subscription period to month.', $subscription['order_id'] ) );
self::log_store_owner_review( $subscription );
$subscription['period'] = 'month';
$subscription['status'] = 'cancelled';
return $subscription;
}
// let's get the last 2 renewal orders
$last_renewal_order = array_shift( $renewal_orders );
$last_renewal_date = $last_renewal_order->order_date;
$last_renewal_timestamp = strtotime( $last_renewal_date );
$second_renewal_order = array_shift( $renewal_orders );
$second_renewal_date = $second_renewal_order->order_date;
$second_renewal_timestamp = strtotime( $second_renewal_date );
$interval = 1;
// if we have an interval, let's pass this along too, because then it's a known variable
if ( array_key_exists( 'interval', $subscription ) && ! empty( $subscription['interval'] ) ) {
$interval = $subscription['interval'];
}
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: calling wcs_estimate_period_between().', $subscription['order_id'] ) );
$period = wcs_estimate_period_between( $last_renewal_date, $second_renewal_date, $interval );
// if we have 3 renewal orders, do a double check
if ( ! empty( $renewal_orders ) ) {
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: we have 3 renewal orders, trying to make sure we are right.', $subscription['order_id'] ) );
$third_renewal_order = array_shift( $renewal_orders );
$third_renewal_date = $third_renewal_order->order_date;
$period2 = wcs_estimate_period_between( $second_renewal_date, $third_renewal_date, $interval );
if ( $period == $period2 ) {
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: second check confirmed, we are very confident period is %s.', $subscription['order_id'], $period ) );
$subscription['period'] = $period;
}
}
$subscription['period'] = $period;
return $subscription;
}
/**
* '_subscription_interval': we can attempt to derive this from the time between renewal orders. For example, if there are two renewal
* orders found 3 months apart, the billing period would be month. If there are not two or more renewal orders (we can't use a single
* renewal order because that would account for the free trial) and a _product_id value , if the product still exists, we can use the
* current value set on that product. It won't always be correct, but it's the closest we can get to an accurate estimate.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_interval( $subscription, $item_id, $item_meta ) {
// Get info from the product
if ( array_key_exists( '_subscription_interval', $item_meta ) && ! empty( $item_meta['_subscription_interval'] ) ) {
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: getting interval from item meta and returning.', $subscription['order_id'] ) );
$subscription['interval'] = $item_meta['_subscription_interval'][0];
return $subscription;
}
// by this time we already have a period on our hand
// let's get the renewal orders
$renewal_orders = self::get_renewal_orders( $subscription );
if ( count( $renewal_orders ) < 2 ) {
// default to 1
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: setting default subscription interval to 1.', $subscription['order_id'] ) );
self::log_store_owner_review( $subscription );
$subscription['interval'] = 1;
$subscription['status'] = 'cancelled';
return $subscription;
}
// let's get the last 2 renewal orders
$last_renewal_order = array_shift( $renewal_orders );
$last_renewal_date = $last_renewal_order->order_date;
$last_renewal_timestamp = strtotime( $last_renewal_date );
$second_renewal_order = array_shift( $renewal_orders );
$second_renewal_date = $second_renewal_order->order_date;
$second_renewal_timestamp = strtotime( $second_renewal_date );
$subscription['interval'] = wcs_estimate_periods_between( $second_renewal_timestamp, $last_renewal_timestamp, $subscription['period'] );
return $subscription;
}
/**
* '_subscription_length': if there are '_subscription_expiry_date' and '_subscription_start_date' values, we can use those to
* determine how many billing periods fall between them, and therefore, the length of the subscription. This data is low value however as
* it is no longer stored in v2.0 and mainly used to determine the expiration date.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_length( $subscription, $item_id, $item_meta ) {
// Let's see if the item meta has that
$subscription = self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'length', '_subscription_length', '' );
if ( '' !== $subscription['length'] ) {
return $subscription;
}
$effective_start_date = self::get_effective_start_date( $subscription );
// If we can calculate it from the effective date and expiry date
if ( 'expired' == $subscription['status'] && array_key_exists( 'expiry_date', $subscription ) && ! empty( $subscription['expiry_date'] ) && null !== $effective_start_date && array_key_exists( 'period', $subscription ) && ! empty( $subscription['period'] ) && array_key_exists( 'interval', $subscription ) && ! empty( $subscription['interval'] ) ) {
$intervals = wcs_estimate_periods_between( strtotime( $effective_start_date ), strtotime( $subscription['expiry_date'] ), $subscription['period'], 'floor' );
$subscription['length'] = $intervals;
} else {
$subscription['length'] = 0;
}
return $subscription;
}
/**
* '_subscription_start_date': the original order's '_paid_date' value (stored in post meta) can be used as the subscription's start date.
* If no '_paid_date' exists, because the order used a payment method that doesn't call $order->payment_complete(), like BACs or Cheque,
* then we can use the post_date_gmt column in the wp_posts table of the original order.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_start_date( $subscription, $item_id, $item_meta ) {
global $wpdb;
$start_date = get_post_meta( $subscription['order_id'], '_paid_date', true );
WCS_Upgrade_Logger::add( sprintf( 'Repairing start_date for order %d: Trying to use the _paid date for start date.', $subscription['order_id'] ) );
if ( empty( $start_date ) ) {
WCS_Upgrade_Logger::add( '-- start_date from _paid date failed. Using post_date_gmt' );
$start_date = $wpdb->get_var( $wpdb->prepare( "SELECT post_date_gmt FROM {$wpdb->posts} WHERE ID = %d", $subscription['order_id'] ) );
}
$subscription['start_date'] = $start_date;
return $subscription;
}
/**
* '_subscription_trial_expiry_date': if the subscription has at least one renewal order, we can set the trial expiration date to the date
* of the first renewal order. However, this is generally safe to default to 0 if it is not set. Especially if the subscription is
* inactive and/or has 1 or more renewals (because its no longer used and is simply for record keeping).
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_trial_expiry_date( $subscription, $item_id, $item_meta ) {
$subscription['trial_expiry_date'] = self::maybe_get_date_from_action_scheduler( 'scheduled_subscription_trial_end', $subscription );
return $subscription;
}
/**
* '_subscription_expiry_date': if the subscription has a '_subscription_length' value, that can be used to calculate the expiration date
* (from the '_subscription_start_date' or '_subscription_trial_expiry_date' if one is set). If no length is set, but the subscription has
* an expired status, the '_subscription_end_date' can be used. In most other cases, this is generally safe to default to 0 if the
* subscription is cancelled because its no longer used and is simply for record keeping.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_expiry_date( $subscription, $item_id, $item_meta ) {
$subscription['expiry_date'] = self::maybe_get_date_from_action_scheduler( 'scheduled_subscription_expiration', $subscription );
return $subscription;
}
/**
* '_subscription_end_date': if the subscription has a '_subscription_length' value and status of expired, the length can be used to
* calculate the end date as it will be the same as the expiration date. If no length is set, or the subscription has a cancelled status,
* some time within 24 hours after the last renewal order's date can be used to provide a rough estimate.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_end_date( $subscription, $item_id, $item_meta ) {
$subscription = self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'end_date', '_subscription_end_date', '' );
if ( '' !== $subscription['end_date'] ) {
return $subscription;
}
if ( 'expired' == $subscription['status'] && array_key_exists( 'expiry_date', $subscription ) && ! empty( $subscription['expiry_date'] ) ) {
$subscription['end_date'] = $subscription['expiry_date'];
} elseif ( 'cancelled' == $subscription['status'] || ! array_key_exists( 'length', $subscription ) || empty( $subscription['length'] ) ) {
// get renewal orders
$renewal_orders = self::get_renewal_orders( $subscription );
$last_order = array_shift( $renewal_orders );
if ( empty( $last_order ) ) {
$subscription['end_date'] = 0;
} else {
$subscription['end_date'] = wcs_add_time( 5, 'hours', strtotime( $last_order->order_date ) );
}
} else {
// if everything failed, let's have an empty one
$subscription['end_date'] = 0;
}
return $subscription;
}
/**
* _recurring_line_total': if the subscription has at least one renewal order, this value can be derived from the '_line_total' value of
* that order. If no renewal orders exist, it can be derived roughly by deducting the '_subscription_sign_up_fee' value from the original
* order's total if there is no trial expiration date.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_recurring_line_total( $subscription, $item_id, $item_meta ) {
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'recurring_line_total', '_line_total', 0 );
}
/**
* _recurring_line_total': if the subscription has at least one renewal order, this value can be derived from the '_line_total' value
* of that order. If no renewal orders exist, it can be derived roughly by deducting the '_subscription_sign_up_fee' value from the
* original order's total if there is no trial expiration date.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_recurring_line_tax( $subscription, $item_id, $item_meta ) {
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'recurring_line_tax', '_line_tax', 0 );
}
/**
* _recurring_line_total': if the subscription has at least one renewal order, this value can be derived from the '_line_total' value of
* that order. If no renewal orders exist, it can be derived roughly by deducting the '_subscription_sign_up_fee' value from the original
* order's total if there is no trial expiration date
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_recurring_line_subtotal( $subscription, $item_id, $item_meta ) {
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'recurring_line_subtotal', '_line_subtotal', 0 );
}
/**
* _recurring_line_total': if the subscription has at least one renewal order, this value can be derived from the '_line_total' value of
* that order. If no renewal orders exist, it can be derived roughly by deducting the '_subscription_sign_up_fee' value from the original
* order's total if there is no trial expiration date.
*
* @param array $subscription data about the subscription
* @param numeric $item_id the id of the product we're missing variation id for
* @param array $item_meta meta data about the product
* @return array repaired data about the subscription
*/
public static function repair_recurring_line_subtotal_tax( $subscription, $item_id, $item_meta ) {
return self::repair_from_item_meta( $subscription, $item_id, $item_meta, 'recurring_line_subtotal_tax', '_line_subtotal_tax', 0 );
}
/**
* Utility function to calculate the seconds between two timestamps. Order is not important, it's just the difference.
*
* @param string $to mysql timestamp
* @param string $from mysql timestamp
* @return integer number of seconds between the two
*/
private static function time_diff( $to, $from ) {
$to = strtotime( $to );
$from = strtotime( $from );
return abs( $to - $from );
}
/**
* Utility function to get all renewal orders in the old structure.
*
* @param array $subscription the sub we're looking for the renewal orders
* @return array of WC_Orders
*/
private static function get_renewal_orders( $subscription ) {
$related_orders = array();
$related_post_ids = get_posts( array(
'posts_per_page' => -1,
'post_type' => 'shop_order',
'post_status' => 'any',
'fields' => 'ids',
'orderby' => 'date',
'order' => 'DESC',
'post_parent' => $subscription['order_id'],
) );
foreach ( $related_post_ids as $post_id ) {
$related_orders[ $post_id ] = wc_get_order( $post_id );
}
return $related_orders;
}
/**
* Utility method to check the action scheduler for dates
*
* @param string $type the type of scheduled action
* @param string $subscription_key key of subscription in the format of order_id_item_id
* @return string either 0 or mysql date
*/
private static function maybe_get_date_from_action_scheduler( $type, $subscription ) {
$action_args = array(
'user_id' => intval( $subscription['user_id'] ),
'subscription_key' => $subscription['subscription_key'],
);
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: Repairing date type "%s" from action scheduler...', $subscription['order_id'], $type ) );
WCS_Upgrade_Logger::add( '-- This is the arguments: ' . PHP_EOL . print_r( array( $action_args, 'hook' => $type ), true ) . PHP_EOL );
$next_date_timestamp = wc_next_scheduled_action( $type, $action_args );
if ( false === $next_date_timestamp ) {
// set it to 0 as default
$formatted_date = 0;
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: Repairing date type "%s": fetch of date unsuccessfull: no action present. Date is 0.', $subscription['order_id'], $type ) );
} else {
$formatted_date = date( 'Y-m-d H:i:s', $next_date_timestamp );
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: Repairing date type "%s": fetch of date successfull. New date is %s', $subscription['order_id'], $type, $formatted_date ) );
}
return $formatted_date;
}
/**
* Utility function to return the effective start date for interval calculations (end of trial period -> start date -> null )
*
* @param array $subscription subscription data
* @return mixed mysql formatted date, or null if none found
*/
public static function get_effective_start_date( $subscription ) {
if ( array_key_exists( 'trial_expiry_date', $subscription ) && ! empty( $subscription['trial_expiry_date'] ) ) {
$effective_date = $subscription['trial_expiry_date'];
} elseif ( array_key_exists( 'trial_period', $subscription ) && ! empty( $subscription['trial_period'] ) && array_key_exists( 'trial_length', $subscription ) && ! empty( $subscription['trial_length'] ) && array_key_exists( 'start_date', $subscription ) && ! empty( $subscription['start_date'] ) ) {
// calculate the end of trial from interval, period and start date
$effective_date = date( 'Y-m-d H:i:s', strtotime( '+' . $subscription['trial_length'] . ' ' . $subscription['trial_period'], strtotime( $subscription['start_date'] ) ) );
} elseif ( array_key_exists( 'start_date', $subscription ) && ! empty( $subscription['start_date'] ) ) {
$effective_date = $subscription['start_date'];
} else {
$effective_date = null;
}
return $effective_date;
}
/**
* Logs an entry for the store owner to review an issue.
*
* @param array $subscription subscription data
*/
protected static function log_store_owner_review( $subscription ) {
WCS_Upgrade_Logger::add( sprintf( '-- For order %d: shop owner please review subscription.', $subscription['order_id'] ) );
}
}