Passed
Push — master ( 72d56b...acf0a7 )
by Brian
10:07
created

getpaid_get_invoice_subscription_groups()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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