Passed
Push — master ( f27242...1d577c )
by Brian
04:34
created

getpaid_calculate_subscription_totals()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 50
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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