Passed
Push — master ( b424aa...1c746e )
by Brian
04:20
created

getpaid_count_subscription_invoices()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 6
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 8
rs 10
1
<?php
2
/**
3
 * Contains subscription functions.
4
 *
5
 * @since 1.0.0
6
 * @package Invoicing
7
 */
8
9
/**
10
 * Retrieves an invoice's subscriptions.
11
 *
12
 * @param       WPInv_Invoice $invoice
13
 * @return      WPInv_Subscription[]|WPInv_Subscription|false
14
 * @since       2.3.0
15
 */
16
function getpaid_get_invoice_subscriptions( $invoice ) {
17
18
    // Retrieve subscription groups.
19
    $subscription_ids = wp_list_pluck( getpaid_get_invoice_subscription_groups( $invoice->get_id() ), 'subscription_id' );
20
21
    // No subscription groups, normal subscription.
22
    if ( empty( $subscription_ids ) ) {
23
        return getpaid_subscriptions()->get_invoice_subscription( $invoice );
0 ignored issues
show
Bug Best Practice introduced by
The expression return getpaid_subscript..._subscription($invoice) also could return the type boolean which is incompatible with the documented return type WPInv_Subscription|WPInv_Subscription[]|false.
Loading history...
24
    }
25
26
    // Subscription groups.
27
    return array_filter( array_map( 'getpaid_get_subscription', $subscription_ids ) );
28
29
}
30
31
/**
32
 * Retrieves an invoice's subscription groups.
33
 *
34
 * @param       int $invoice_id
35
 * @return      array
36
 * @since       2.3.0
37
 */
38
function getpaid_get_invoice_subscription_groups( $invoice_id ) {
39
    $subscription_groups = get_post_meta( $invoice_id, 'getpaid_subscription_groups', true );
40
    return empty( $subscription_groups ) ? array() : $subscription_groups;
0 ignored issues
show
Bug Best Practice introduced by
The expression return empty($subscripti... : $subscription_groups also could return the type string which is incompatible with the documented return type array.
Loading history...
41
}
42
43
/**
44
 * Retrieves an invoice's subscription's subscription groups.
45
 *
46
 * @param       int $invoice_id
47
 * @param       int $subscription_id
48
 * @return      array|false
49
 * @since       2.3.0
50
 */
51
function getpaid_get_invoice_subscription_group( $invoice_id, $subscription_id ) {
52
    $subscription_groups = getpaid_get_invoice_subscription_groups( $invoice_id );
53
	$matching_group      = wp_list_filter( $subscription_groups, compact( 'subscription_id' ) );
54
    return reset( $matching_group );
55
}
56
57
/**
58
 * Retrieves a subscription given an id.
59
 *
60
 * @param int|string|object|WPInv_Subscription $subscription Subscription object, id, profile_id, or object to read.
61
 * @since       2.3.0
62
 * @return WPInv_Subscription|false
63
 */
64
function getpaid_get_subscription( $subscription ) {
65
	$subscription = new WPInv_Subscription( $subscription );
66
	return $subscription->exists() ? $subscription : false;
67
}
68
69
/**
70
 * Queries the subscriptions database.
71
 *
72
 * @param array $args Query arguments.For a list of all supported args, refer to GetPaid_Subscriptions_Query::prepare_query()
73
 * @param string $return 'results' returns the found subscriptions, $count returns the total count while 'query' returns GetPaid_Subscriptions_Query
74
 *
75
 *
76
 * @return int|array|WPInv_Subscription[]|GetPaid_Subscriptions_Query
77
 */
78
function getpaid_get_subscriptions( $args = array(), $return = 'results' ) {
79
80
	// Do not retrieve all fields if we just want the count.
81
	if ( 'count' == $return ) {
82
		$args['fields'] = 'id';
83
		$args['number'] = 1;
84
	}
85
86
	// Do not count all matches if we just want the results.
87
	if ( 'results' == $return ) {
88
		$args['count_total'] = false;
89
	}
90
91
	$query = new GetPaid_Subscriptions_Query( $args );
92
93
	if ( 'results' == $return ) {
94
		return $query->get_results();
95
	}
96
97
	if ( 'count' == $return ) {
98
		return $query->get_total();
99
	}
100
101
	return $query;
102
}
103
104
/**
105
 * Returns an array of valid subscription statuses.
106
 *
107
 * @return array
108
 */
109
function getpaid_get_subscription_statuses() {
110
111
	return apply_filters(
112
		'getpaid_get_subscription_statuses',
113
		array(
114
			'pending'    => __( 'Pending', 'invoicing' ),
115
			'trialling'  => __( 'Trialing', 'invoicing' ),
116
			'active'     => __( 'Active', 'invoicing' ),
117
			'failing'    => __( 'Failing', 'invoicing' ),
118
			'expired'    => __( 'Expired', 'invoicing' ),
119
			'completed'  => __( 'Complete', 'invoicing' ),
120
			'cancelled'  => __( 'Cancelled', 'invoicing' ),
121
		)
122
	);
123
124
}
125
126
/**
127
 * Returns a subscription status label
128
 *
129
 * @return string
130
 */
131
function getpaid_get_subscription_status_label( $status ) {
132
	$statuses = getpaid_get_subscription_statuses();
133
	return isset( $statuses[ $status ] ) ? $statuses[ $status ] : ucfirst( sanitize_text_field( $status ) );
134
}
135
136
/**
137
 * Returns an array of valid subscription status classes.
138
 *
139
 * @return array
140
 */
141
function getpaid_get_subscription_status_classes() {
142
143
	return apply_filters(
144
		'getpaid_get_subscription_status_classes',
145
		array(
146
			'pending'    => 'badge-dark',
147
			'trialling'  => 'badge-info',
148
			'active'     => 'badge-success',
149
			'failing'    => 'badge-warning',
150
			'expired'    => 'badge-danger',
151
			'completed'  => 'badge-primary',
152
			'cancelled'  => 'badge-secondary',
153
		)
154
	);
155
156
}
157
158
/**
159
 * Counts subscriptions in each status.
160
 *
161
 * @return array
162
 */
163
function getpaid_get_subscription_status_counts( $args = array() ) {
164
165
	$statuses = array_keys( getpaid_get_subscription_statuses() );
166
	$counts   = array();
167
168
	foreach ( $statuses as $status ) {
169
		$_args             = wp_parse_args( "status=$status", $args );
170
		$counts[ $status ] = getpaid_get_subscriptions( $_args, 'count' );
171
	}
172
173
	return $counts;
174
175
}
176
177
/**
178
 * Returns valid subscription periods.
179
 *
180
 * @return array
181
 */
182
function getpaid_get_subscription_periods() {
183
184
	return apply_filters(
185
		'getpaid_get_subscription_periods',
186
		array(
187
188
			'day'   => array(
189
				'singular' => __( '%s day', 'invoicing' ),
190
				'plural'   => __( '%d days', 'invoicing' ),
191
			),
192
193
			'week'   => array(
194
				'singular' => __( '%s week', 'invoicing' ),
195
				'plural'   => __( '%d weeks', 'invoicing' ),
196
			),
197
198
			'month'   => array(
199
				'singular' => __( '%s month', 'invoicing' ),
200
				'plural'   => __( '%d months', 'invoicing' ),
201
			),
202
203
			'year'   => array(
204
				'singular' => __( '%s year', 'invoicing' ),
205
				'plural'   => __( '%d years', 'invoicing' ),
206
			),
207
208
		)
209
	);
210
211
}
212
213
/**
214
 * Given a subscription trial, e.g, 1 month, returns the interval (1)
215
 *
216
 * @param string $trial_period
217
 * @return int
218
 */
219
function getpaid_get_subscription_trial_period_interval( $trial_period ) {
220
	return (int) preg_replace( '/[^0-9]/', '', $trial_period );
221
}
222
223
/**
224
 * Given a subscription trial, e.g, 1 month, returns the period (month)
225
 *
226
 * @param string $trial_period
227
 * @return string
228
 */
229
function getpaid_get_subscription_trial_period_period( $trial_period ) {
230
	return preg_replace( '/[^a-z]/', '', strtolower( $trial_period ) );
231
}
232
233
/**
234
 * Returns a singular period label..
235
 *
236
 * @param string $period
237
 * @param int $interval
238
 * @return string
239
 */
240
function getpaid_get_subscription_period_label( $period, $interval = 1, $singular_prefix = '1' ) {
241
	$label = (int) $interval > 1 ? getpaid_get_plural_subscription_period_label(  $period, $interval ) : getpaid_get_singular_subscription_period_label( $period, $singular_prefix );
242
	return strtolower( sanitize_text_field( $label ) );
243
}
244
245
/**
246
 * Returns a singular period label..
247
 *
248
 * @param string $period
249
 * @return string
250
 */
251
function getpaid_get_singular_subscription_period_label( $period, $singular_prefix = '1' ) {
252
253
	$periods = getpaid_get_subscription_periods();
254
	$period  = strtolower( $period );
255
256
	if ( isset( $periods[ $period ] ) ) {
257
		return sprintf( $periods[ $period ]['singular'], $singular_prefix );
258
	}
259
260
	// Backwards compatibility.
261
	foreach ( $periods as $key => $data ) {
262
		if ( strpos( $key, $period ) === 0 ) {
263
			return sprintf( $data['singular'], $singular_prefix );
264
		}
265
	}
266
267
	// Invalid string.
268
	return '';
269
}
270
271
/**
272
 * Returns a plural period label..
273
 *
274
 * @param string $period
275
 * @param int $interval
276
 * @return string
277
 */
278
function getpaid_get_plural_subscription_period_label( $period, $interval ) {
279
280
	$periods = getpaid_get_subscription_periods();
281
	$period  = strtolower( $period );
282
283
	if ( isset( $periods[ $period ] ) ) {
284
		return sprintf( $periods[ $period ]['plural'], $interval );
285
	}
286
287
	// Backwards compatibility.
288
	foreach ( $periods as $key => $data ) {
289
		if ( strpos( $key, $period ) === 0 ) {
290
			return sprintf( $data['plural'], $interval );
291
		}
292
	}
293
294
	// Invalid string.
295
	return '';
296
}
297
298
/**
299
 * Returns formatted subscription amout
300
 *
301
 * @param WPInv_Subscription $subscription
302
 * @return string
303
 */
304
function getpaid_get_formatted_subscription_amount( $subscription ) {
305
306
	$initial    = wpinv_price( $subscription->get_initial_amount(), $subscription->get_parent_payment()->get_currency() );
307
	$recurring  = wpinv_price( $subscription->get_recurring_amount(), $subscription->get_parent_payment()->get_currency() );
308
	$period     = getpaid_get_subscription_period_label( $subscription->get_period(), $subscription->get_frequency(), '' );
309
	$bill_times = $subscription->get_bill_times();
310
311
	if ( ! empty( $bill_times ) ) {
312
		$bill_times = $subscription->get_frequency() * $bill_times;
313
		$bill_times = getpaid_get_subscription_period_label( $subscription->get_period(), $bill_times );
314
	}
315
316
	// Trial periods.
317
	if ( $subscription->has_trial_period() ) {
318
319
		$trial_period   = getpaid_get_subscription_trial_period_period( $subscription->get_trial_period() );
320
		$trial_interval = getpaid_get_subscription_trial_period_interval( $subscription->get_trial_period() );
321
322
		if ( empty( $bill_times ) ) {
323
324
			return sprintf(
325
326
				// translators: $1: is the initial amount, $2: is the trial period, $3: is the recurring amount, $4: is the recurring period
327
				_x( '%1$s trial for %2$s then %3$s / %4$s', 'Subscription amount. (e.g.: $10 trial for 1 month then $120 / year)', 'invoicing' ),
328
				$initial,
329
				getpaid_get_subscription_period_label( $trial_period, $trial_interval ),
330
				$recurring,
331
				$period
332
	
333
			);
334
335
		}
336
337
		return sprintf(
338
339
			// translators: $1: is the initial amount, $2: is the trial period, $3: is the recurring amount, $4: is the recurring period, $5: is the bill times
340
			_x( '%1$s trial for %2$s then %3$s / %4$s for %5$s', 'Subscription amount. (e.g.: $10 trial for 1 month then $120 / year for 4 years)', 'invoicing' ),
341
			$initial,
342
			getpaid_get_subscription_period_label( $trial_period, $trial_interval ),
343
			$recurring,
344
			$period,
345
			$bill_times
346
		);
347
348
	}
349
350
	if ( $initial != $recurring ) {
351
352
		if ( empty( $bill_times ) ) {
353
354
			return sprintf(
355
356
				// translators: $1: is the initial amount, $2: is the recurring amount, $3: is the recurring period
357
				_x( 'Initial payment of %1$s which renews at %2$s / %3$s', 'Subscription amount. (e.g.:Initial payment of $100 which renews at $120 / year)', 'invoicing' ),
358
				$initial,
359
				$recurring,
360
				$period
361
	
362
			);
363
364
		}
365
366
		return sprintf(
367
368
			// translators: $1: is the initial amount, $2: is the recurring amount, $3: is the recurring period, $4: is the bill times
369
			_x( 'Initial payment of %1$s which renews at %2$s / %3$s for %4$s', 'Subscription amount. (e.g.:Initial payment of $100 which renews at $120 / year for 5 years)', 'invoicing' ),
370
			$initial,
371
			$recurring,
372
			$period,
373
			$bill_times
374
375
		);
376
377
	}
378
379
	if ( empty( $bill_times ) ) {
380
381
		return sprintf(
382
383
			// translators: $1: is the recurring amount, $2: is the recurring period
384
			_x( '%1$s / %2$s', 'Subscription amount. (e.g.: $120 / year)', 'invoicing' ),
385
			$initial,
386
			$period
387
	
388
		);
389
390
	}
391
392
	return sprintf(
393
394
		// translators: $1: is the bill times, $2: is the recurring amount, $3: is the recurring period
395
		_x( '%2$s / %3$s for %1$s', 'Subscription amount. (e.g.: $120 / year for 5 years)', 'invoicing' ),
396
		$bill_times,
397
		$initial,
398
		$period
399
400
	);
401
402
}
403
404
/**
405
 * Returns an invoice subscription.
406
 *
407
 * @param WPInv_Invoice $invoice
408
 * @return WPInv_Subscription|false
409
 */
410
function getpaid_get_invoice_subscription( $invoice ) {
411
	return getpaid_subscriptions()->get_invoice_subscription( $invoice );
0 ignored issues
show
Bug Best Practice introduced by
The expression return getpaid_subscript..._subscription($invoice) also could return the type boolean which is incompatible with the documented return type WPInv_Subscription|false.
Loading history...
412
}
413
414
/**
415
 * Activates an invoice subscription.
416
 *
417
 * @param WPInv_Invoice $invoice
418
 */
419
function getpaid_activate_invoice_subscription( $invoice ) {
420
	$subscription = getpaid_get_invoice_subscription( $invoice );
421
	if ( is_a( $subscription, 'WPInv_Subscription' ) ) {
422
		$subscription->activate();
423
	}
424
}
425
426
/**
427
 * Returns the subscriptions controller.
428
 *
429
 * @return WPInv_Subscriptions
430
 */
431
function getpaid_subscriptions() {
432
	return getpaid()->get( 'subscriptions' );
433
}
434
435
/**
436
 * Fetchs an invoice subscription from the database.
437
 *
438
 * @since 2.3.0
439
 * @return WPInv_Subscription|bool
440
 */
441
function wpinv_get_invoice_subscription( $invoice ) {
442
443
    // Retrieve the invoice.
444
    $invoice = new WPInv_Invoice( $invoice );
445
446
    // Ensure it is a recurring invoice.
447
    if ( ! $invoice->is_recurring() ) {
448
        return false;
449
    }
450
451
	// Fetch the invoice subscription.
452
	$subscription = getpaid_get_subscriptions(
453
		array(
454
			'invoice_in' => $invoice->is_renewal() ? $invoice->get_parent_id() : $invoice->get_id(),
455
			'number'     => 1,
456
		)
457
	);
458
459
	return empty( $subscription ) ? false : $subscription[0];
460
461
}
462
463
/**
464
 * Construct a cart key based on the billing schedule of a subscription product.
465
 *
466
 * Subscriptions groups products by billing schedule when calculating cart totals, so that gateway fees and other "per invoice" amounts
467
 * can be calculated for each group of items for each renewal. This method constructs a cart key based on the billing schedule
468
 * to allow products on the same billing schedule to be grouped together - free trials are accounted for by
469
 * the trial interval and period of the subscription.
470
 *
471
 * @param GetPaid_Form_Item|WPInv_Item $cart_item
472
 * @return string
473
 */
474
function getpaid_get_recurring_item_key( $cart_item ) {
475
476
	$cart_key     = 'renews_';
477
	$interval     = $cart_item->get_recurring_interval();
478
	$period       = $cart_item->get_recurring_period( true );
479
	$length       = $cart_item->get_recurring_limit() * $interval;
480
	$trial_period = $cart_item->get_trial_period( true );
481
	$trial_length = $cart_item->get_trial_interval();
482
483
	// First start with the billing interval and period
484
	switch ( $interval ) {
485
		case 1 :
486
			if ( 'day' == $period ) {
487
				$cart_key .= 'daily';
488
			} else {
489
				$cart_key .= sprintf( '%sly', $period );
490
			}
491
			break;
492
		case 2 :
493
			$cart_key .= sprintf( 'every_2nd_%s', $period );
494
			break;
495
		case 3 :
496
			$cart_key .= sprintf( 'every_3rd_%s', $period );
497
		break;
498
		default:
499
			$cart_key .= sprintf( 'every_%dth_%s', $interval, $period );
500
			break;
501
	}
502
503
	// Maybe add the optional maximum billing periods...
504
	if ( $length > 0 ) {
505
		$cart_key .= '_for_';
506
		$cart_key .= sprintf( '%d_%s', $length, $period );
507
		if ( $length > 1 ) {
508
			$cart_key .= 's';
509
		}
510
	}
511
512
	// And an optional free trial.
513
	if ( $cart_item->has_free_trial() ) {
514
		$cart_key .= sprintf( '_after_a_%d_%s_trial', $trial_length, $trial_period );
515
	}
516
517
	return apply_filters( 'getpaid_get_recurring_item_key', $cart_key, $cart_item );
518
}
519
520
/**
521
 * Retrieves subscription groups for all items in an invoice/payment form submission.
522
 *
523
 * @param WPInv_Invoice|GetPaid_Payment_Form_Submission|GetPaid_Payment_Form $invoice
524
 * @return array
525
 */
526
function getpaid_get_subscription_groups( $invoice ) {
527
528
	// Generate subscription groups.
529
	$subscription_groups = array();
530
	foreach ( $invoice->get_items() as $item ) {
531
532
		if ( $item->is_recurring() ) {
533
			$subscription_groups[ getpaid_get_recurring_item_key( $item ) ][] = $item;
534
		}
535
536
	}
537
538
	return $subscription_groups;
539
}
540
541
/**
542
 * Calculate the initial and recurring totals for all subscription products in an invoice/payment form submission.
543
 *
544
 * We group subscriptions by billing schedule to make the display and creation of recurring totals sane,
545
 * when there are multiple subscriptions in the cart.
546
 *
547
 * @param WPInv_Invoice|GetPaid_Payment_Form_Submission|GetPaid_Payment_Form $invoice
548
 * @return array
549
 */
550
function getpaid_calculate_subscription_totals( $invoice ) {
551
552
	// Generate subscription groups.
553
	$subscription_groups = getpaid_get_subscription_groups( $invoice );
554
555
	// Now let's calculate the totals for each group of subscriptions
556
	$subscription_totals = array();
557
558
	foreach ( $subscription_groups as $subscription_key => $items ) {
559
560
		if ( empty( $subscription_totals[ $subscription_key ] ) ) {
561
562
			$subscription_totals[ $subscription_key ] = array(
563
				'initial_total'   => 0,
564
				'recurring_total' => 0,
565
				'items'           => array(),
566
				'trialling'       => false,
567
			);
568
569
		}
570
571
		/**
572
		 * Get the totals of the group.
573
		 * @var GetPaid_Form_Item $item
574
		 */
575
		foreach ( $items as $item ) {
576
577
			$subscription_totals[ $subscription_key ]['items'][$item->get_id()]  = $item->prepare_data_for_saving();
578
			$subscription_totals[ $subscription_key ]['item_id']                 = $item->get_id();
579
			$subscription_totals[ $subscription_key ]['period']                  = $item->get_recurring_period( true );
580
			$subscription_totals[ $subscription_key ]['interval']                = $item->get_recurring_interval();
581
			$subscription_totals[ $subscription_key ]['initial_total']          += $item->get_sub_total();
582
			$subscription_totals[ $subscription_key ]['recurring_total']        += $item->get_recurring_sub_total();
583
			$subscription_totals[ $subscription_key ]['recurring_limit']         = $item->get_recurring_limit();
584
585
			// Calculate the next renewal date.
586
			$period       = $item->get_recurring_period( true );
587
			$interval     = $item->get_recurring_interval();
588
589
			// If the subscription item has a trial period...
590
			if ( $item->has_free_trial() ) {
591
				$period   = $item->get_trial_period( true );
592
				$interval = $item->get_trial_interval();
593
				$subscription_totals[ $subscription_key ]['trialling'] = $interval . ' ' . $period;
594
			}
595
596
			$subscription_totals[ $subscription_key ]['renews_on'] = date( 'Y-m-d H:i:s', strtotime( "+$interval $period", current_time( 'timestamp' ) ) );
597
598
		}
599
600
	}
601
602
	return apply_filters( 'getpaid_calculate_subscription_totals', $subscription_totals, $invoice );
603
}
604
605
/**
606
 * Checks if we should group a subscription.
607
 *
608
 * @param WPInv_Invoice|GetPaid_Payment_Form_Submission|GetPaid_Payment_Form $invoice
609
 * @return array
610
 */
611
function getpaid_should_group_subscriptions( $invoice ) {
612
613
	$recurring_items = 0;
614
615
	foreach ( $invoice->get_items() as $item ) {
616
617
		if ( $item->is_recurring() ) {
618
			$recurring_items ++;
619
		}
620
621
	}
622
623
	return apply_filters( 'getpaid_should_group_subscriptions', $recurring_items > 1, $invoice );
0 ignored issues
show
Bug Best Practice introduced by
The expression return apply_filters('ge...ng_items > 1, $invoice) also could return the type boolean which is incompatible with the documented return type array.
Loading history...
624
}
625
626
/**
627
 * Counts the invoices belonging to a subscription.
628
 *
629
 * @param int $parent_invoice_id
630
 * @return int
631
 */
632
function getpaid_count_subscription_invoices( $parent_invoice_id ) {
633
	global $wpdb;
634
635
	return (int) $wpdb->get_var(
636
		$wpdb->prepare(
637
			"SELECT COUNT(ID) FROM $wpdb->posts WHERE ( post_parent=%d OR ID=%d ) AND post_status IN ( 'publish', 'wpi-processing', 'wpi-renewal' )",
638
			$parent_invoice_id,
639
			$parent_invoice_id
640
		)
641
	);
642
643
}
644