Issues (850)

Security Analysis    4 potential vulnerabilities

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection (1)
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection (2)
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting (1)
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/subscription-functions.php (4 issues)

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