Completed
Push — issues/611 ( f0b8b3...915615 )
by Ravinder
19:46
created

functions.php ➔ give_get_total_earnings()   C

Complexity

Conditions 7
Paths 12

Size

Total Lines 44
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 21
nc 12
nop 1
dl 0
loc 44
rs 6.7272
c 0
b 0
f 0
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 46 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
/**
3
 * Payment Functions
4
 *
5
 * @package     Give
6
 * @subpackage  Payments
7
 * @copyright   Copyright (c) 2016, WordImpress
8
 * @license     https://opensource.org/licenses/gpl-license GNU Public License
9
 * @since       1.0
10
 */
11
12
// Exit if accessed directly.
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit;
15
}
16
17
/**
18
 * Get Payments
19
 *
20
 * Retrieve payments from the database.
21
 *
22
 * Since 1.0, this function takes an array of arguments, instead of individual
23
 * parameters. All of the original parameters remain, but can be passed in any
24
 * order via the array.
25
 *
26
 * @since 1.0
27
 *
28
 * @param array $args     {
29
 *                        Optional. Array of arguments passed to payments query.
30
 *
31
 * @type int    $offset   The number of payments to offset before retrieval.
32
 *                            Default is 0.
33
 * @type int    $number   The number of payments to query for. Use -1 to request all
34
 *                            payments. Default is 20.
35
 * @type string $mode     Default is 'live'.
36
 * @type string $order    Designates ascending or descending order of payments.
37
 *                            Accepts 'ASC', 'DESC'. Default is 'DESC'.
38
 * @type string $orderby  Sort retrieved payments by parameter. Default is 'ID'.
39
 * @type string $status   The status of the payments. Default is 'any'.
40
 * @type string $user     User. Default is null.
41
 * @type string $meta_key Custom field key. Default is null.
42
 * }
43
 *
44
 * @return array $payments Payments retrieved from the database
45
 */
46
function give_get_payments( $args = array() ) {
47
48
	// Fallback to post objects to ensure backwards compatibility.
49
	if ( ! isset( $args['output'] ) ) {
50
		$args['output'] = 'posts';
51
	}
52
53
	$args     = apply_filters( 'give_get_payments_args', $args );
54
	$payments = new Give_Payments_Query( $args );
55
56
	return $payments->get_payments();
57
}
58
59
/**
60
 * Retrieve payment by a given field
61
 *
62
 * @since  1.0
63
 *
64
 * @param  string $field The field to retrieve the payment with.
65
 * @param  mixed  $value The value for $field.
66
 *
67
 * @return mixed
68
 */
69
function give_get_payment_by( $field = '', $value = '' ) {
70
71
	if ( empty( $field ) || empty( $value ) ) {
72
		return false;
73
	}
74
75
	switch ( strtolower( $field ) ) {
76
77
		case 'id':
78
			$payment = new Give_Payment( $value );
79
			$id      = $payment->ID;
80
81
			if ( empty( $id ) ) {
82
				return false;
83
			}
84
85
			break;
86
87
		case 'key':
88
			$payment = give_get_payments( array(
89
				'meta_key'       => '_give_payment_purchase_key',
90
				'meta_value'     => $value,
91
				'posts_per_page' => 1,
92
				'fields'         => 'ids',
93
			) );
94
95
			if ( $payment ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $payment of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
96
				$payment = new Give_Payment( $payment[0] );
97
			}
98
99
			break;
100
101
		case 'payment_number':
102
			$payment = give_get_payments( array(
103
				'meta_key'       => '_give_payment_number',
104
				'meta_value'     => $value,
105
				'posts_per_page' => 1,
106
				'fields'         => 'ids',
107
			) );
108
109
			if ( $payment ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $payment of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
110
				$payment = new Give_Payment( $payment[0] );
111
			}
112
113
			break;
114
115
		default:
116
			return false;
117
	}
118
119
	if ( $payment ) {
120
		return $payment;
121
	}
122
123
	return false;
124
}
125
126
/**
127
 * Insert Payment
128
 *
129
 * @since  1.0
130
 *
131
 * @param  array $payment_data Arguments passed.
132
 *
133
 * @return int|bool Payment ID if payment is inserted, false otherwise.
134
 */
135
function give_insert_payment( $payment_data = array() ) {
136
137
	if ( empty( $payment_data ) ) {
138
		return false;
139
	}
140
141
	$payment    = new Give_Payment();
142
	$gateway    = ! empty( $payment_data['gateway'] ) ? $payment_data['gateway'] : '';
143
	$gateway    = empty( $gateway ) && isset( $_POST['give-gateway'] ) ? $_POST['give-gateway'] : $gateway;
144
	$form_id    = isset( $payment_data['give_form_id'] ) ? $payment_data['give_form_id'] : 0;
145
	$price_id   = give_get_payment_meta_price_id( $payment_data );
146
	$form_title = isset( $payment_data['give_form_title'] ) ? $payment_data['give_form_title'] : get_the_title( $form_id );
147
148
	// Set properties.
149
	$payment->total          = $payment_data['price'];
150
	$payment->status         = ! empty( $payment_data['status'] ) ? $payment_data['status'] : 'pending';
151
	$payment->currency       = ! empty( $payment_data['currency'] ) ? $payment_data['currency'] : give_get_currency();
152
	$payment->user_info      = $payment_data['user_info'];
153
	$payment->gateway        = $gateway;
154
	$payment->form_title     = $form_title;
155
	$payment->form_id        = $form_id;
156
	$payment->price_id       = $price_id;
157
	$payment->user_id        = $payment_data['user_info']['id'];
158
	$payment->email          = $payment_data['user_email'];
159
	$payment->first_name     = $payment_data['user_info']['first_name'];
160
	$payment->last_name      = $payment_data['user_info']['last_name'];
161
	$payment->email          = $payment_data['user_info']['email'];
162
	$payment->ip             = give_get_ip();
163
	$payment->key            = $payment_data['purchase_key'];
164
	$payment->mode           = give_is_test_mode() ? 'test' : 'live';
165
	$payment->parent_payment = ! empty( $payment_data['parent'] ) ? absint( $payment_data['parent'] ) : '';
166
167
	// Add the donation.
168
	$args = array(
169
		'price'    => $payment->total,
170
		'price_id' => $payment->price_id,
171
		'fees'     => isset( $payment_data['fees'] ) ? $payment_data['fees'] : array(),
172
	);
173
174
	$payment->add_donation( $payment->form_id, $args );
175
176
	// Set date if present.
177
	if ( isset( $payment_data['post_date'] ) ) {
178
		$payment->date = $payment_data['post_date'];
179
	}
180
181
	// Handle sequential payments.
182
	if ( give_get_option( 'enable_sequential' ) ) {
183
		$number          = give_get_next_payment_number();
184
		$payment->number = give_format_payment_number( $number );
185
		update_option( 'give_last_payment_number', $number );
186
	}
187
188
	// Clear the user's donation cache.
189
	delete_transient( 'give_user_' . $payment_data['user_info']['id'] . '_purchases' );
190
191
	// Save payment.
192
	$payment->save();
193
194
	/**
195
	 * Fires while inserting payments.
196
	 *
197
	 * @since 1.0
198
	 *
199
	 * @param int   $payment_id   The payment ID.
200
	 * @param array $payment_data Arguments passed.
201
	 */
202
	do_action( 'give_insert_payment', $payment->ID, $payment_data );
203
204
	// Return payment ID upon success.
205
	if ( ! empty( $payment->ID ) ) {
206
		return $payment->ID;
207
	}
208
209
	// Return false if no payment was inserted.
210
	return false;
211
212
}
213
214
/**
215
 * Create payment.
216
 *
217
 * @param $payment_data
218
 *
219
 * @return bool|int
220
 */
221
function give_create_payment( $payment_data ) {
222
223
	$form_id  = intval( $payment_data['post_data']['give-form-id'] );
224
	$price_id = isset( $payment_data['post_data']['give-price-id'] ) ? $payment_data['post_data']['give-price-id'] : '';
225
226
	// Collect payment data.
227
	$insert_payment_data = array(
228
		'price'           => $payment_data['price'],
229
		'give_form_title' => $payment_data['post_data']['give-form-title'],
230
		'give_form_id'    => $form_id,
231
		'give_price_id'   => $price_id,
232
		'date'            => $payment_data['date'],
233
		'user_email'      => $payment_data['user_email'],
234
		'purchase_key'    => $payment_data['purchase_key'],
235
		'currency'        => give_get_currency(),
236
		'user_info'       => $payment_data['user_info'],
237
		'status'          => 'pending',
238
		'gateway'         => 'paypal',
239
	);
240
241
	/**
242
	 * Filter the payment params.
243
	 *
244
	 * @since 1.8
245
	 *
246
	 * @param array $insert_payment_data
247
	 */
248
	$insert_payment_data = apply_filters( 'give_create_payment', $insert_payment_data );
249
250
	// Record the pending payment.
251
	return give_insert_payment( $insert_payment_data );
252
}
253
254
/**
255
 * Updates a payment status.
256
 *
257
 * @since  1.0
258
 *
259
 * @param  int    $payment_id Payment ID.
260
 * @param  string $new_status New Payment Status. Default is 'publish'.
261
 *
262
 * @return bool
263
 */
264
function give_update_payment_status( $payment_id, $new_status = 'publish' ) {
265
266
	$payment         = new Give_Payment( $payment_id );
267
	$payment->status = $new_status;
268
	$updated         = $payment->save();
269
270
	return $updated;
271
}
272
273
274
/**
275
 * Deletes a Donation
276
 *
277
 * @since  1.0
278
 * @global      $give_logs
279
 *
280
 * @param  int  $payment_id      Payment ID (default: 0).
281
 * @param  bool $update_customer If we should update the customer stats (default:true).
282
 *
283
 * @return void
284
 */
285
function give_delete_purchase( $payment_id = 0, $update_customer = true ) {
286
	global $give_logs;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
287
288
	$payment     = new Give_Payment( $payment_id );
289
	$amount      = give_get_payment_amount( $payment_id );
290
	$status      = $payment->post_status;
291
	$customer_id = give_get_payment_customer_id( $payment_id );
292
	$customer    = new Give_Customer( $customer_id );
293
294
	// Only undo donations that aren't these statuses.
295
	$dont_undo_statuses = apply_filters( 'give_undo_purchase_statuses', array(
296
		'pending',
297
		'cancelled',
298
	) );
299
300
	if ( ! in_array( $status, $dont_undo_statuses ) ) {
301
		give_undo_purchase( false, $payment_id );
302
	}
303
304
	if ( $status == 'publish' ) {
305
306
		// Only decrease earnings if they haven't already been decreased (or were never increased for this payment).
307
		give_decrease_total_earnings( $amount );
308
309
		// @todo: Refresh only range related stat cache
310
		give_delete_donation_stats();
311
312
		if ( $customer->id && $update_customer ) {
313
314
			// Decrement the stats for the donor.
315
			$customer->decrease_purchase_count();
316
			$customer->decrease_value( $amount );
317
318
		}
319
	}
320
321
	/**
322
	 * Fires before deleting payment.
323
	 *
324
	 * @since 1.0
325
	 *
326
	 * @param int $payment_id Payment ID.
327
	 */
328
	do_action( 'give_payment_delete', $payment_id );
329
330
	if ( $customer->id && $update_customer ) {
331
332
		// Remove the payment ID from the donor.
333
		$customer->remove_payment( $payment_id );
334
335
	}
336
337
	// Remove the payment.
338
	wp_delete_post( $payment_id, true );
339
340
	// Remove related sale log entries.
341
	$give_logs->delete_logs(
342
		null,
343
		'sale',
344
		array(
345
			array(
346
				'key'   => '_give_log_payment_id',
347
				'value' => $payment_id,
348
			),
349
		)
350
	);
351
352
	/**
353
	 * Fires after payment deleted.
354
	 *
355
	 * @since 1.0
356
	 *
357
	 * @param int $payment_id Payment ID.
358
	 */
359
	do_action( 'give_payment_deleted', $payment_id );
360
}
361
362
/**
363
 * Undo Donation
364
 *
365
 * Undoes a donation, including the decrease of donations and earning stats.
366
 * Used for when refunding or deleting a donation.
367
 *
368
 * @since  1.0
369
 *
370
 * @param  int|bool $form_id    Form ID (default: false).
371
 * @param  int      $payment_id Payment ID.
372
 *
373
 * @return void
374
 */
375
function give_undo_purchase( $form_id = false, $payment_id ) {
376
377
	if ( ! empty( $form_id ) ) {
378
		$form_id = false;
379
		_give_deprected_argument( 'form_id', 'give_undo_purchase', '1.5' );
380
	}
381
382
	$payment = new Give_Payment( $payment_id );
383
384
	$maybe_decrease_earnings = apply_filters( 'give_decrease_earnings_on_undo', true, $payment, $payment->form_id );
385
	if ( true === $maybe_decrease_earnings ) {
386
		// Decrease earnings.
387
		give_decrease_earnings( $payment->form_id, $payment->total );
388
	}
389
390
	$maybe_decrease_sales = apply_filters( 'give_decrease_sales_on_undo', true, $payment, $payment->form_id );
391
	if ( true === $maybe_decrease_sales ) {
392
		// Decrease donation count.
393
		give_decrease_purchase_count( $payment->form_id );
394
	}
395
396
}
397
398
399
/**
400
 * Count Payments
401
 *
402
 * Returns the total number of payments recorded.
403
 *
404
 * @since  1.0
405
 *
406
 * @param  array $args Arguments passed.
407
 *
408
 * @return object $stats Contains the number of payments per payment status.
409
 */
410
function give_count_payments( $args = array() ) {
411
412
	global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
413
414
	$defaults = array(
415
		'user'       => null,
416
		's'          => null,
417
		'start-date' => null,
418
		'end-date'   => null,
419
		'form_id'    => null,
420
	);
421
422
	$args = wp_parse_args( $args, $defaults );
423
424
	$select = 'SELECT p.post_status,count( * ) AS num_posts';
425
	$join   = '';
426
	$where  = "WHERE p.post_type = 'give_payment'";
427
428
	// Count payments for a specific user.
429
	if ( ! empty( $args['user'] ) ) {
430
431
		if ( is_email( $args['user'] ) ) {
432
			$field = 'email';
433
		} elseif ( is_numeric( $args['user'] ) ) {
434
			$field = 'id';
435
		} else {
436
			$field = '';
437
		}
438
439
		$join = "LEFT JOIN $wpdb->postmeta m ON (p.ID = m.post_id)";
440
441
		if ( ! empty( $field ) ) {
442
			$where .= "
443
				AND m.meta_key = '_give_payment_user_{$field}'
444
				AND m.meta_value = '{$args['user']}'";
445
		}
446
447
		// Count payments for a search.
448
	} elseif ( ! empty( $args['s'] ) ) {
449
450
		if ( is_email( $args['s'] ) || strlen( $args['s'] ) == 32 ) {
451
452
			if ( is_email( $args['s'] ) ) {
453
				$field = '_give_payment_user_email';
454
			} else {
455
				$field = '_give_payment_purchase_key';
456
			}
457
458
			$join  = "LEFT JOIN $wpdb->postmeta m ON (p.ID = m.post_id)";
459
			$where .= $wpdb->prepare( '
460
                AND m.meta_key = %s
461
                AND m.meta_value = %s',
462
				$field,
463
				$args['s']
464
			);
465
466
		} elseif ( '#' == substr( $args['s'], 0, 1 ) ) {
467
468
			$search = str_replace( '#:', '', $args['s'] );
469
			$search = str_replace( '#', '', $search );
470
471
			$select = 'SELECT p2.post_status,count( * ) AS num_posts ';
472
			$join   = "LEFT JOIN $wpdb->postmeta m ON m.meta_key = '_give_log_payment_id' AND m.post_id = p.ID ";
473
			$join   .= "INNER JOIN $wpdb->posts p2 ON m.meta_value = p2.ID ";
474
			$where  = "WHERE p.post_type = 'give_log' ";
475
			$where  .= $wpdb->prepare( 'AND p.post_parent = %d} ', $search );
476
477
		} elseif ( is_numeric( $args['s'] ) ) {
478
479
			$join  = "LEFT JOIN $wpdb->postmeta m ON (p.ID = m.post_id)";
480
			$where .= $wpdb->prepare( "
481
				AND m.meta_key = '_give_payment_user_id'
482
				AND m.meta_value = %d",
483
				$args['s']
484
			);
485
486
		} else {
487
			$search = $wpdb->esc_like( $args['s'] );
488
			$search = '%' . $search . '%';
489
490
			$where .= $wpdb->prepare( 'AND ((p.post_title LIKE %s) OR (p.post_content LIKE %s))', $search, $search );
491
		}
492
	}
493
494
	if ( ! empty( $args['form_id'] ) && is_numeric( $args['form_id'] ) ) {
495
496
		$where .= $wpdb->prepare( ' AND p.post_parent = %d', $args['form_id'] );
497
498
	}
499
	// Limit payments count by date.
500
	if ( ! empty( $args['start-date'] ) && false !== strpos( $args['start-date'], '/' ) ) {
501
502
		$date_parts = explode( '/', $args['start-date'] );
503
		$month      = ! empty( $date_parts[0] ) && is_numeric( $date_parts[0] ) ? $date_parts[0] : 0;
504
		$day        = ! empty( $date_parts[1] ) && is_numeric( $date_parts[1] ) ? $date_parts[1] : 0;
505
		$year       = ! empty( $date_parts[2] ) && is_numeric( $date_parts[2] ) ? $date_parts[2] : 0;
506
507
		$is_date = checkdate( $month, $day, $year );
508
		if ( false !== $is_date ) {
509
510
			$date  = new DateTime( $args['start-date'] );
511
			$where .= $wpdb->prepare( " AND p.post_date >= '%s'", $date->format( 'Y-m-d' ) );
512
513
		}
514
515
		// Fixes an issue with the payments list table counts when no end date is specified (partiy with stats class).
516
		if ( empty( $args['end-date'] ) ) {
517
			$args['end-date'] = $args['start-date'];
518
		}
519
	}
520
521
	if ( ! empty( $args['end-date'] ) && false !== strpos( $args['end-date'], '/' ) ) {
522
523
		$date_parts = explode( '/', $args['end-date'] );
524
525
		$month = ! empty( $date_parts[0] ) ? $date_parts[0] : 0;
526
		$day   = ! empty( $date_parts[1] ) ? $date_parts[1] : 0;
527
		$year  = ! empty( $date_parts[2] ) ? $date_parts[2] : 0;
528
529
		$is_date = checkdate( $month, $day, $year );
530
		if ( false !== $is_date ) {
531
532
			$date  = new DateTime( $args['end-date'] );
533
			$where .= $wpdb->prepare( " AND p.post_date <= '%s'", $date->format( 'Y-m-d' ) );
534
535
		}
536
	}
537
538
	$where = apply_filters( 'give_count_payments_where', $where );
539
	$join  = apply_filters( 'give_count_payments_join', $join );
540
541
	$query = "$select
542
		FROM $wpdb->posts p
543
		$join
544
		$where
545
		GROUP BY p.post_status
546
	";
547
548
	$cache_key = md5( $query );
549
550
	$count = wp_cache_get( $cache_key, 'counts' );
551
	if ( false !== $count ) {
552
		return $count;
553
	}
554
555
	$count = $wpdb->get_results( $query, ARRAY_A );
556
557
	$stats    = array();
558
	$statuses = get_post_stati();
559
	if ( isset( $statuses['private'] ) && empty( $args['s'] ) ) {
560
		unset( $statuses['private'] );
561
	}
562
563
	foreach ( $statuses as $state ) {
564
		$stats[ $state ] = 0;
565
	}
566
567
	foreach ( (array) $count as $row ) {
568
569
		if ( 'private' == $row['post_status'] && empty( $args['s'] ) ) {
570
			continue;
571
		}
572
573
		$stats[ $row['post_status'] ] = $row['num_posts'];
574
	}
575
576
	$stats = (object) $stats;
577
	wp_cache_set( $cache_key, $stats, 'counts' );
578
579
	return $stats;
580
}
581
582
583
/**
584
 * Check For Existing Payment
585
 *
586
 * @since  1.0
587
 *
588
 * @param  int $payment_id Payment ID
589
 *
590
 * @return bool $exists True if payment exists, false otherwise.
591
 */
592
function give_check_for_existing_payment( $payment_id ) {
593
	$exists  = false;
594
	$payment = new Give_Payment( $payment_id );
595
596
	if ( $payment_id === $payment->ID && 'publish' === $payment->status ) {
597
		$exists = true;
598
	}
599
600
	return $exists;
601
}
602
603
/**
604
 * Get Payment Status
605
 *
606
 * @since 1.0
607
 *
608
 * @param WP_Post|Give_Payment $payment      Payment object.
609
 * @param bool                 $return_label Whether to return the translated status label
610
 *                                           instead of status value. Default false.
611
 *
612
 * @return bool|mixed True if payment status exists, false otherwise.
613
 */
614
function give_get_payment_status( $payment, $return_label = false ) {
615
616
	if ( ! is_object( $payment ) || ! isset( $payment->post_status ) ) {
617
		return false;
618
	}
619
620
	$statuses = give_get_payment_statuses();
621
622
	if ( ! is_array( $statuses ) || empty( $statuses ) ) {
623
		return false;
624
	}
625
626
	// Get payment object if no already given.
627
	$payment = $payment instanceof Give_Payment ? $payment : new Give_Payment( $payment->ID );
628
629
	if ( array_key_exists( $payment->status, $statuses ) ) {
630
		if ( true === $return_label ) {
631
			// Return translated status label.
632
			return $statuses[ $payment->status ];
633
		} else {
634
			// Account that our 'publish' status is labeled 'Complete'
635
			$post_status = 'publish' == $payment->status ? 'Complete' : $payment->post_status;
636
637
			// Make sure we're matching cases, since they matter
638
			return array_search( strtolower( $post_status ), array_map( 'strtolower', $statuses ) );
639
		}
640
	}
641
642
	return false;
643
}
644
645
/**
646
 * Retrieves all available statuses for payments.
647
 *
648
 * @since  1.0
649
 *
650
 * @return array $payment_status All the available payment statuses.
651
 */
652
function give_get_payment_statuses() {
653
	$payment_statuses = array(
654
		'pending'     => __( 'Pending', 'give' ),
655
		'publish'     => __( 'Complete', 'give' ),
656
		'refunded'    => __( 'Refunded', 'give' ),
657
		'failed'      => __( 'Failed', 'give' ),
658
		'cancelled'   => __( 'Cancelled', 'give' ),
659
		'abandoned'   => __( 'Abandoned', 'give' ),
660
		'preapproval' => __( 'Pre-Approved', 'give' ),
661
		'revoked'     => __( 'Revoked', 'give' ),
662
	);
663
664
	return apply_filters( 'give_payment_statuses', $payment_statuses );
665
}
666
667
/**
668
 * Get Payment Status Keys
669
 *
670
 * Retrieves keys for all available statuses for payments
671
 *
672
 * @since  1.0
673
 *
674
 * @return array $payment_status All the available payment statuses.
675
 */
676
function give_get_payment_status_keys() {
677
	$statuses = array_keys( give_get_payment_statuses() );
678
	asort( $statuses );
679
680
	return array_values( $statuses );
681
}
682
683
/**
684
 * Get Earnings By Date
685
 *
686
 * @since  1.0
687
 *
688
 * @param  int $day       Day number. Default is null.
689
 * @param  int $month_num Month number. Default is null.
690
 * @param  int $year      Year number. Default is null.
691
 * @param  int $hour      Hour number. Default is null.
692
 *
693
 * @return int $earnings  Earnings
0 ignored issues
show
Documentation introduced by
Should the return type not be double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
694
 */
695
function give_get_earnings_by_date( $day = null, $month_num, $year = null, $hour = null ) {
696
697
	// This is getting deprecated soon. Use Give_Payment_Stats with the get_earnings() method instead.
698
	global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
699
700
	$args = array(
701
		'post_type'              => 'give_payment',
702
		'nopaging'               => true,
703
		'year'                   => $year,
704
		'monthnum'               => $month_num,
705
		'post_status'            => array( 'publish' ),
706
		'fields'                 => 'ids',
707
		'update_post_term_cache' => false,
708
	);
709
	if ( ! empty( $day ) ) {
710
		$args['day'] = $day;
711
	}
712
713
	if ( ! empty( $hour ) ) {
714
		$args['hour'] = $hour;
715
	}
716
717
	$args = apply_filters( 'give_get_earnings_by_date_args', $args );
718
	$key  = Give_Cache::get_key( 'give_stats', $args );
719
720
	if ( ! empty( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'give-refresh-reports' ) ) {
721
		$earnings = false;
722
	} else {
723
		$earnings = Give_Cache::get( $key );
724
	}
725
726
	if ( false === $earnings ) {
727
		$sales    = get_posts( $args );
728
		$earnings = 0;
729
		if ( $sales ) {
730
			$sales = implode( ',', $sales );
731
732
			$earnings = $wpdb->get_var( "SELECT SUM(meta_value) FROM $wpdb->postmeta WHERE meta_key = '_give_payment_total' AND post_id IN ({$sales})" );
733
734
		}
735
		// Cache the results for one hour.
736
		Give_Cache::set( $key, $earnings, HOUR_IN_SECONDS );
737
	}
738
739
	return round( $earnings, 2 );
740
}
741
742
/**
743
 * Get Donations (sales) By Date
744
 *
745
 * @since  1.0
746
 *
747
 * @param  int $day       Day number. Default is null.
748
 * @param  int $month_num Month number. Default is null.
749
 * @param  int $year      Year number. Default is null.
750
 * @param  int $hour      Hour number. Default is null.
751
 *
752
 * @return int $count     Sales
753
 */
754
function give_get_sales_by_date( $day = null, $month_num = null, $year = null, $hour = null ) {
755
756
	// This is getting deprecated soon. Use Give_Payment_Stats with the get_sales() method instead.
757
	$args = array(
758
		'post_type'              => 'give_payment',
759
		'nopaging'               => true,
760
		'year'                   => $year,
761
		'fields'                 => 'ids',
762
		'post_status'            => array( 'publish' ),
763
		'update_post_meta_cache' => false,
764
		'update_post_term_cache' => false,
765
	);
766
767
	$show_free = apply_filters( 'give_sales_by_date_show_free', true, $args );
768
769
	if ( false === $show_free ) {
770
		$args['meta_query'] = array(
771
			array(
772
				'key'     => '_give_payment_total',
773
				'value'   => 0,
774
				'compare' => '>',
775
				'type'    => 'NUMERIC',
776
			),
777
		);
778
	}
779
780
	if ( ! empty( $month_num ) ) {
781
		$args['monthnum'] = $month_num;
782
	}
783
784
	if ( ! empty( $day ) ) {
785
		$args['day'] = $day;
786
	}
787
788
	if ( ! empty( $hour ) ) {
789
		$args['hour'] = $hour;
790
	}
791
792
	$args = apply_filters( 'give_get_sales_by_date_args', $args );
793
794
	$key  = Give_Cache::get_key( 'give_stats', $args );
795
796
	if ( ! empty( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'give-refresh-reports' ) ) {
797
		$count = false;
798
	} else {
799
		$count = Give_Cache::get( $key );
800
	}
801
802
	if ( false === $count ) {
803
		$sales = new WP_Query( $args );
804
		$count = (int) $sales->post_count;
805
		// Cache the results for one hour.
806
		Give_Cache::set( $key, $count, HOUR_IN_SECONDS );
807
	}
808
809
	return $count;
810
}
811
812
/**
813
 * Checks whether a payment has been marked as complete.
814
 *
815
 * @since  1.0
816
 *
817
 * @param  int $payment_id Payment ID to check against.
818
 *
819
 * @return bool $ret True if complete, false otherwise.
820
 */
821
function give_is_payment_complete( $payment_id ) {
822
	$payment = new Give_Payment( $payment_id );
823
824
	$ret = false;
825
826
	if ( $payment->ID > 0 ) {
827
828
		if ( (int) $payment_id === (int) $payment->ID && 'publish' == $payment->status ) {
829
			$ret = true;
830
		}
831
	}
832
833
	return apply_filters( 'give_is_payment_complete', $ret, $payment_id, $payment->post_status );
834
}
835
836
/**
837
 * Get Total Donations.
838
 *
839
 * @since  1.0
840
 *
841
 * @return int $count Total sales.
842
 */
843
function give_get_total_sales() {
844
845
	$payments = give_count_payments();
846
847
	return $payments->publish;
848
}
849
850
/**
851
 * Get Total Earnings
852
 *
853
 * @since  1.0
854
 *
855
 * @param bool $recalculate Recalculate earnings forcefully.
856
 *
857
 * @return float $total Total earnings.
858
 */
859
function give_get_total_earnings( $recalculate = false ) {
860
861
	$total = get_option( 'give_earnings_total', 0 );
862
863
	// Calculate total earnings.
864
	if ( ! $total || $recalculate ) {
865
		global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
866
867
		$total = (float) 0;
868
869
		$args = apply_filters( 'give_get_total_earnings_args', array(
870
			'offset' => 0,
871
			'number' => - 1,
872
			'status' => array( 'publish' ),
873
			'fields' => 'ids',
874
		) );
875
876
		$payments = give_get_payments( $args );
877
		if ( $payments ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $payments of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
878
879
			/**
880
			 * If performing a donation, we need to skip the very last payment in the database,
881
			 * since it calls give_increase_total_earnings() on completion,
882
			 * which results in duplicated earnings for the very first donation.
883
			 */
884
			if ( did_action( 'give_update_payment_status' ) ) {
885
				array_pop( $payments );
886
			}
887
888
			if ( ! empty( $payments ) ) {
889
				$payments = implode( ',', $payments );
890
				$total    += $wpdb->get_var( "SELECT SUM(meta_value) FROM $wpdb->postmeta WHERE meta_key = '_give_payment_total' AND post_id IN({$payments})" );
891
			}
892
		}
893
894
		update_option( 'give_earnings_total', $total, 'no' );
895
	}
896
897
	if ( $total < 0 ) {
898
		$total = 0; // Don't ever show negative earnings.
899
	}
900
901
	return apply_filters( 'give_total_earnings', round( $total, give_currency_decimal_filter() ) );
902
}
903
904
/**
905
 * Increase the Total Earnings
906
 *
907
 * @since  1.0
908
 *
909
 * @param  int $amount   The amount you would like to increase the total earnings by.
910
 *                       Default is 0.
911
 *
912
 * @return float $total  Total earnings.
913
 */
914
function give_increase_total_earnings( $amount = 0 ) {
915
	$total = give_get_total_earnings();
916
	$total += $amount;
917
	update_option( 'give_earnings_total', $total );
918
919
	return $total;
920
}
921
922
/**
923
 * Decrease the Total Earnings
924
 *
925
 * @since 1.0
926
 *
927
 * @param int $amount The amount you would like to decrease the total earnings by.
928
 *
929
 * @return float $total Total earnings.
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
930
 */
931
function give_decrease_total_earnings( $amount = 0 ) {
932
	$total = give_get_total_earnings();
933
	$total -= $amount;
934
	if ( $total < 0 ) {
935
		$total = 0;
936
	}
937
	update_option( 'give_earnings_total', $total );
938
939
	return $total;
940
}
941
942
/**
943
 * Get Payment Meta for a specific Payment
944
 *
945
 * @since 1.0
946
 *
947
 * @param int    $payment_id Payment ID.
948
 * @param string $meta_key   The meta key to pull.
949
 * @param bool   $single     Pull single meta entry or as an object.
950
 *
951
 * @return mixed $meta Payment Meta.
952
 */
953
function give_get_payment_meta( $payment_id = 0, $meta_key = '_give_payment_meta', $single = true ) {
954
	$payment = new Give_Payment( $payment_id );
955
956
	return $payment->get_meta( $meta_key, $single );
957
}
958
959
/**
960
 * Update the meta for a payment
961
 *
962
 * @param  int    $payment_id Payment ID.
963
 * @param  string $meta_key   Meta key to update.
964
 * @param  string $meta_value Value to update to.
965
 * @param  string $prev_value Previous value.
966
 *
967
 * @return mixed Meta ID if successful, false if unsuccessful.
968
 */
969
function give_update_payment_meta( $payment_id = 0, $meta_key = '', $meta_value = '', $prev_value = '' ) {
970
	$payment = new Give_Payment( $payment_id );
971
972
	return $payment->update_meta( $meta_key, $meta_value, $prev_value );
973
}
974
975
/**
976
 * Get the user_info Key from Payment Meta
977
 *
978
 * @since 1.0
979
 *
980
 * @param int $payment_id Payment ID.
981
 *
982
 * @return string $user_info User Info Meta Values.
0 ignored issues
show
Documentation introduced by
Should the return type not be array?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
983
 */
984
function give_get_payment_meta_user_info( $payment_id ) {
985
	$payment = new Give_Payment( $payment_id );
986
987
	return $payment->user_info;
988
}
989
990
/**
991
 * Get the donations Key from Payment Meta
992
 *
993
 * Retrieves the form_id from a (Previously titled give_get_payment_meta_donations)
994
 *
995
 * @since 1.0
996
 *
997
 * @param int $payment_id Payment ID.
998
 *
999
 * @return int $form_id Form ID.
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1000
 */
1001
function give_get_payment_form_id( $payment_id ) {
1002
	$payment = new Give_Payment( $payment_id );
1003
1004
	return $payment->form_id;
1005
}
1006
1007
/**
1008
 * Get the user email associated with a payment
1009
 *
1010
 * @since 1.0
1011
 *
1012
 * @param int $payment_id Payment ID.
1013
 *
1014
 * @return string $email User email.
1015
 */
1016
function give_get_payment_user_email( $payment_id ) {
1017
	$payment = new Give_Payment( $payment_id );
1018
1019
	return $payment->email;
1020
}
1021
1022
/**
1023
 * Is the payment provided associated with a user account
1024
 *
1025
 * @since  1.3
1026
 *
1027
 * @param  int $payment_id The payment ID.
1028
 *
1029
 * @return bool $is_guest_payment If the payment is associated with a user (false) or not (true)
1030
 */
1031
function give_is_guest_payment( $payment_id ) {
1032
	$payment_user_id  = give_get_payment_user_id( $payment_id );
1033
	$is_guest_payment = ! empty( $payment_user_id ) && $payment_user_id > 0 ? false : true;
1034
1035
	return (bool) apply_filters( 'give_is_guest_payment', $is_guest_payment, $payment_id );
1036
}
1037
1038
/**
1039
 * Get the user ID associated with a payment
1040
 *
1041
 * @since 1.3
1042
 *
1043
 * @param int $payment_id Payment ID.
1044
 *
1045
 * @return int $user_id User ID.
1046
 */
1047
function give_get_payment_user_id( $payment_id ) {
1048
	$payment = new Give_Payment( $payment_id );
1049
1050
	return $payment->user_id;
1051
}
1052
1053
/**
1054
 * Get the donor ID associated with a payment
1055
 *
1056
 * @since 1.0
1057
 *
1058
 * @param int $payment_id Payment ID.
1059
 *
1060
 * @return int $customer_id Customer ID.
1061
 */
1062
function give_get_payment_customer_id( $payment_id ) {
1063
	$payment = new Give_Payment( $payment_id );
1064
1065
	return $payment->customer_id;
1066
}
1067
1068
/**
1069
 * Get the IP address used to make a donation
1070
 *
1071
 * @since 1.0
1072
 *
1073
 * @param int $payment_id Payment ID.
1074
 *
1075
 * @return string $ip User IP.
1076
 */
1077
function give_get_payment_user_ip( $payment_id ) {
1078
	$payment = new Give_Payment( $payment_id );
1079
1080
	return $payment->ip;
1081
}
1082
1083
/**
1084
 * Get the date a payment was completed
1085
 *
1086
 * @since 1.0
1087
 *
1088
 * @param int $payment_id Payment ID.
1089
 *
1090
 * @return string $date The date the payment was completed.
1091
 */
1092
function give_get_payment_completed_date( $payment_id = 0 ) {
1093
	$payment = new Give_Payment( $payment_id );
1094
1095
	return $payment->completed_date;
1096
}
1097
1098
/**
1099
 * Get the gateway associated with a payment
1100
 *
1101
 * @since 1.0
1102
 *
1103
 * @param int $payment_id Payment ID.
1104
 *
1105
 * @return string $gateway Gateway.
1106
 */
1107
function give_get_payment_gateway( $payment_id ) {
1108
	$payment = new Give_Payment( $payment_id );
1109
1110
	return $payment->gateway;
1111
}
1112
1113
/**
1114
 * Get the currency code a payment was made in
1115
 *
1116
 * @since 1.0
1117
 *
1118
 * @param int $payment_id Payment ID.
1119
 *
1120
 * @return string $currency The currency code.
1121
 */
1122
function give_get_payment_currency_code( $payment_id = 0 ) {
1123
	$payment = new Give_Payment( $payment_id );
1124
1125
	return $payment->currency;
1126
}
1127
1128
/**
1129
 * Get the currency name a payment was made in
1130
 *
1131
 * @since 1.0
1132
 *
1133
 * @param int $payment_id Payment ID.
1134
 *
1135
 * @return string $currency The currency name.
1136
 */
1137
function give_get_payment_currency( $payment_id = 0 ) {
1138
	$currency = give_get_payment_currency_code( $payment_id );
1139
1140
	return apply_filters( 'give_payment_currency', give_get_currency_name( $currency ), $payment_id );
1141
}
1142
1143
/**
1144
 * Get the key for a donation
1145
 *
1146
 * @since 1.0
1147
 *
1148
 * @param int $payment_id Payment ID.
1149
 *
1150
 * @return string $key Donation key.
1151
 */
1152
function give_get_payment_key( $payment_id = 0 ) {
1153
	$payment = new Give_Payment( $payment_id );
1154
1155
	return $payment->key;
1156
}
1157
1158
/**
1159
 * Get the payment order number
1160
 *
1161
 * This will return the payment ID if sequential order numbers are not enabled or the order number does not exist
1162
 *
1163
 * @since 1.0
1164
 *
1165
 * @param int $payment_id Payment ID.
1166
 *
1167
 * @return string $number Payment order number.
1168
 */
1169
function give_get_payment_number( $payment_id = 0 ) {
1170
	$payment = new Give_Payment( $payment_id );
1171
1172
	return $payment->number;
1173
}
1174
1175
/**
1176
 * Formats the payment number with the prefix and postfix
1177
 *
1178
 * @since  1.3
1179
 *
1180
 * @param  int $number The payment number to format.
1181
 *
1182
 * @return string      The formatted payment number.
1183
 */
1184
function give_format_payment_number( $number ) {
1185
1186
	if ( ! give_get_option( 'enable_sequential' ) ) {
1187
		return $number;
1188
	}
1189
1190
	if ( ! is_numeric( $number ) ) {
1191
		return $number;
1192
	}
1193
1194
	$prefix  = give_get_option( 'sequential_prefix' );
1195
	$number  = absint( $number );
1196
	$postfix = give_get_option( 'sequential_postfix' );
1197
1198
	$formatted_number = $prefix . $number . $postfix;
1199
1200
	return apply_filters( 'give_format_payment_number', $formatted_number, $prefix, $number, $postfix );
1201
}
1202
1203
/**
1204
 * Gets the next available order number
1205
 *
1206
 * This is used when inserting a new payment
1207
 *
1208
 * @since 1.0
1209
 * @return string $number The next available payment number.
1210
 */
1211
function give_get_next_payment_number() {
1212
1213
	if ( ! give_get_option( 'enable_sequential' ) ) {
1214
		return false;
1215
	}
1216
1217
	$number           = get_option( 'give_last_payment_number' );
1218
	$start            = give_get_option( 'sequential_start', 1 );
1219
	$increment_number = true;
1220
1221
	if ( false !== $number ) {
1222
1223
		if ( empty( $number ) ) {
1224
1225
			$number           = $start;
1226
			$increment_number = false;
1227
1228
		}
1229
	} else {
1230
1231
		// This case handles the first addition of the new option, as well as if it get's deleted for any reason.
1232
		$payments     = new Give_Payments_Query( array(
1233
			'number'  => 1,
1234
			'order'   => 'DESC',
1235
			'orderby' => 'ID',
1236
			'output'  => 'posts',
1237
			'fields'  => 'ids',
1238
		) );
1239
		$last_payment = $payments->get_payments();
1240
1241
		if ( ! empty( $last_payment ) ) {
1242
1243
			$number = give_get_payment_number( $last_payment[0] );
1244
1245
		}
1246
1247
		if ( ! empty( $number ) && $number !== (int) $last_payment[0] ) {
1248
1249
			$number = give_remove_payment_prefix_postfix( $number );
1250
1251
		} else {
1252
1253
			$number           = $start;
1254
			$increment_number = false;
1255
		}
1256
	}
1257
1258
	$increment_number = apply_filters( 'give_increment_payment_number', $increment_number, $number );
1259
1260
	if ( $increment_number ) {
1261
		$number ++;
1262
	}
1263
1264
	return apply_filters( 'give_get_next_payment_number', $number );
1265
}
1266
1267
/**
1268
 * Given a given a number, remove the pre/postfix
1269
 *
1270
 * @since  1.3
1271
 *
1272
 * @param  string $number The formatted Current Number to increment.
1273
 *
1274
 * @return string The new Payment number without prefix and postfix.
1275
 */
1276
function give_remove_payment_prefix_postfix( $number ) {
1277
1278
	$prefix  = give_get_option( 'sequential_prefix' );
1279
	$postfix = give_get_option( 'sequential_postfix' );
1280
1281
	// Remove prefix.
1282
	$number = preg_replace( '/' . $prefix . '/', '', $number, 1 );
1283
1284
	// Remove the postfix.
1285
	$length      = strlen( $number );
1286
	$postfix_pos = strrpos( $number, $postfix );
1287
	if ( false !== $postfix_pos ) {
1288
		$number = substr_replace( $number, '', $postfix_pos, $length );
1289
	}
1290
1291
	// Ensure it's a whole number.
1292
	$number = intval( $number );
1293
1294
	return apply_filters( 'give_remove_payment_prefix_postfix', $number, $prefix, $postfix );
1295
1296
}
1297
1298
1299
/**
1300
 * Get Payment Amount
1301
 *
1302
 * Get the fully formatted payment amount. The payment amount is retrieved using give_get_payment_amount() and is then
1303
 * sent through give_currency_filter() and  give_format_amount() to format the amount correctly.
1304
 *
1305
 * @since       1.0
1306
 *
1307
 * @param int $payment_id Payment ID.
1308
 *
1309
 * @return string $amount Fully formatted payment amount.
1310
 */
1311
function give_payment_amount( $payment_id = 0 ) {
1312
	$amount = give_get_payment_amount( $payment_id );
1313
1314
	return give_currency_filter( give_format_amount( $amount ), give_get_payment_currency_code( $payment_id ) );
1315
}
1316
1317
/**
1318
 * Get the amount associated with a payment
1319
 *
1320
 * @access public
1321
 * @since  1.0
1322
 *
1323
 * @param int $payment_id Payment ID.
1324
 *
1325
 * @return mixed|void
1326
 */
1327
function give_get_payment_amount( $payment_id ) {
1328
1329
	$payment = new Give_Payment( $payment_id );
1330
1331
	return apply_filters( 'give_payment_amount', floatval( $payment->total ), $payment_id );
1332
}
1333
1334
/**
1335
 * Payment Subtotal
1336
 *
1337
 * Retrieves subtotal for payment (this is the amount before fees) and then returns a full formatted amount. This
1338
 * function essentially calls give_get_payment_subtotal()
1339
 *
1340
 * @since 1.5
1341
 *
1342
 * @param int $payment_id Payment ID.
1343
 *
1344
 * @see   give_get_payment_subtotal()
1345
 *
1346
 * @return array Fully formatted payment subtotal.
1347
 */
1348
function give_payment_subtotal( $payment_id = 0 ) {
1349
	$subtotal = give_get_payment_subtotal( $payment_id );
1350
1351
	return give_currency_filter( give_format_amount( $subtotal ), give_get_payment_currency_code( $payment_id ) );
1352
}
1353
1354
/**
1355
 * Get Payment Subtotal
1356
 *
1357
 * Retrieves subtotal for payment (this is the amount before fees) and then returns a non formatted amount.
1358
 *
1359
 * @since 1.5
1360
 *
1361
 * @param int $payment_id Payment ID.
1362
 *
1363
 * @return float $subtotal Subtotal for payment (non formatted).
1364
 */
1365
function give_get_payment_subtotal( $payment_id = 0 ) {
1366
	$payment = new G_Payment( $payment_id );
1367
1368
	return $payment->subtotal;
1369
}
1370
1371
/**
1372
 * Retrieves arbitrary fees for the payment
1373
 *
1374
 * @since 1.5
1375
 *
1376
 * @param int    $payment_id Payment ID.
1377
 * @param string $type       Fee type.
1378
 *
1379
 * @return mixed array if payment fees found, false otherwise.
1380
 */
1381
function give_get_payment_fees( $payment_id = 0, $type = 'all' ) {
1382
	$payment = new Give_Payment( $payment_id );
1383
1384
	return $payment->get_fees( $type );
1385
}
1386
1387
/**
1388
 * Retrieves the donation ID
1389
 *
1390
 * @since  1.0
1391
 *
1392
 * @param int $payment_id Payment ID.
1393
 *
1394
 * @return string The donation ID.
1395
 */
1396
function give_get_payment_transaction_id( $payment_id = 0 ) {
1397
	$payment = new Give_Payment( $payment_id );
1398
1399
	return $payment->transaction_id;
1400
}
1401
1402
/**
1403
 * Sets a Transaction ID in post meta for the given Payment ID.
1404
 *
1405
 * @since  1.0
1406
 *
1407
 * @param int    $payment_id     Payment ID.
1408
 * @param string $transaction_id The transaction ID from the gateway.
1409
 *
1410
 * @return bool|mixed
1411
 */
1412
function give_set_payment_transaction_id( $payment_id = 0, $transaction_id = '' ) {
1413
1414
	if ( empty( $payment_id ) || empty( $transaction_id ) ) {
1415
		return false;
1416
	}
1417
1418
	$transaction_id = apply_filters( 'give_set_payment_transaction_id', $transaction_id, $payment_id );
1419
1420
	return give_update_payment_meta( $payment_id, '_give_payment_transaction_id', $transaction_id );
1421
}
1422
1423
/**
1424
 * Retrieve the donation ID based on the key
1425
 *
1426
 * @since 1.0
1427
 * @global object $wpdb Used to query the database using the WordPress Database API.
1428
 *
1429
 * @param string  $key  the key to search for.
1430
 *
1431
 * @return int $purchase Donation ID.
1432
 */
1433
function give_get_purchase_id_by_key( $key ) {
1434
	global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1435
1436
	$purchase = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_give_payment_purchase_key' AND meta_value = %s LIMIT 1", $key ) );
1437
1438
	if ( $purchase != null ) {
1439
		return $purchase;
1440
	}
1441
1442
	return 0;
1443
}
1444
1445
1446
/**
1447
 * Retrieve the donation ID based on the transaction ID
1448
 *
1449
 * @since 1.3
1450
 * @global object $wpdb Used to query the database using the WordPress Database API.
1451
 *
1452
 * @param string  $key  The transaction ID to search for.
1453
 *
1454
 * @return int $purchase Donation ID.
1455
 */
1456
function give_get_purchase_id_by_transaction_id( $key ) {
1457
	global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1458
1459
	$purchase = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_give_payment_transaction_id' AND meta_value = %s LIMIT 1", $key ) );
1460
1461
	if ( $purchase != null ) {
1462
		return $purchase;
1463
	}
1464
1465
	return 0;
1466
}
1467
1468
/**
1469
 * Retrieve all notes attached to a donation
1470
 *
1471
 * @since 1.0
1472
 *
1473
 * @param int    $payment_id The donation ID to retrieve notes for.
1474
 * @param string $search     Search for notes that contain a search term.
1475
 *
1476
 * @return array $notes Donation Notes
1477
 */
1478
function give_get_payment_notes( $payment_id = 0, $search = '' ) {
1479
1480
	if ( empty( $payment_id ) && empty( $search ) ) {
1481
		return false;
1482
	}
1483
1484
	remove_action( 'pre_get_comments', 'give_hide_payment_notes', 10 );
1485
	remove_filter( 'comments_clauses', 'give_hide_payment_notes_pre_41', 10 );
1486
1487
	$notes = get_comments( array( 'post_id' => $payment_id, 'order' => 'ASC', 'search' => $search ) );
1488
1489
	add_action( 'pre_get_comments', 'give_hide_payment_notes', 10 );
1490
	add_filter( 'comments_clauses', 'give_hide_payment_notes_pre_41', 10, 2 );
1491
1492
	return $notes;
1493
}
1494
1495
1496
/**
1497
 * Add a note to a payment
1498
 *
1499
 * @since 1.0
1500
 *
1501
 * @param int    $payment_id The payment ID to store a note for.
1502
 * @param string $note       The note to store.
1503
 *
1504
 * @return int The new note ID
1505
 */
1506
function give_insert_payment_note( $payment_id = 0, $note = '' ) {
1507
	if ( empty( $payment_id ) ) {
1508
		return false;
1509
	}
1510
1511
	/**
1512
	 * Fires before inserting payment note.
1513
	 *
1514
	 * @since 1.0
1515
	 *
1516
	 * @param int    $payment_id Payment ID.
1517
	 * @param string $note       The note.
1518
	 */
1519
	do_action( 'give_pre_insert_payment_note', $payment_id, $note );
1520
1521
	$note_id = wp_insert_comment( wp_filter_comment( array(
1522
		'comment_post_ID'      => $payment_id,
1523
		'comment_content'      => $note,
1524
		'user_id'              => is_admin() ? get_current_user_id() : 0,
1525
		'comment_date'         => current_time( 'mysql' ),
1526
		'comment_date_gmt'     => current_time( 'mysql', 1 ),
1527
		'comment_approved'     => 1,
1528
		'comment_parent'       => 0,
1529
		'comment_author'       => '',
1530
		'comment_author_IP'    => '',
1531
		'comment_author_url'   => '',
1532
		'comment_author_email' => '',
1533
		'comment_type'         => 'give_payment_note',
1534
1535
	) ) );
1536
1537
	/**
1538
	 * Fires after payment note inserted.
1539
	 *
1540
	 * @since 1.0
1541
	 *
1542
	 * @param int    $note_id    Note ID.
1543
	 * @param int    $payment_id Payment ID.
1544
	 * @param string $note       The note.
1545
	 */
1546
	do_action( 'give_insert_payment_note', $note_id, $payment_id, $note );
1547
1548
	return $note_id;
1549
}
1550
1551
/**
1552
 * Deletes a payment note
1553
 *
1554
 * @since 1.0
1555
 *
1556
 * @param int $comment_id The comment ID to delete.
1557
 * @param int $payment_id The payment ID the note is connected to.
1558
 *
1559
 * @return bool True on success, false otherwise.
1560
 */
1561
function give_delete_payment_note( $comment_id = 0, $payment_id = 0 ) {
1562
	if ( empty( $comment_id ) ) {
1563
		return false;
1564
	}
1565
1566
	/**
1567
	 * Fires before deleting donation note.
1568
	 *
1569
	 * @since 1.0
1570
	 *
1571
	 * @param int $comment_id Note ID.
1572
	 * @param int $payment_id Payment ID.
1573
	 */
1574
	do_action( 'give_pre_delete_payment_note', $comment_id, $payment_id );
1575
1576
	$ret = wp_delete_comment( $comment_id, true );
1577
1578
	/**
1579
	 * Fires after donation note deleted.
1580
	 *
1581
	 * @since 1.0
1582
	 *
1583
	 * @param int $comment_id Note ID.
1584
	 * @param int $payment_id Payment ID.
1585
	 */
1586
	do_action( 'give_post_delete_payment_note', $comment_id, $payment_id );
1587
1588
	return $ret;
1589
}
1590
1591
/**
1592
 * Gets the payment note HTML
1593
 *
1594
 * @since 1.0
1595
 *
1596
 * @param object|int $note       The comment object or ID.
1597
 * @param int        $payment_id The payment ID the note is connected to.
1598
 *
1599
 * @return string
1600
 */
1601
function give_get_payment_note_html( $note, $payment_id = 0 ) {
1602
1603
	if ( is_numeric( $note ) ) {
1604
		$note = get_comment( $note );
1605
	}
1606
1607
	if ( ! empty( $note->user_id ) ) {
1608
		$user = get_userdata( $note->user_id );
1609
		$user = $user->display_name;
1610
	} else {
1611
		$user = esc_html__( 'System', 'give' );
1612
	}
1613
1614
	$date_format = give_date_format() . ', ' . get_option( 'time_format' );
1615
1616
	$delete_note_url = wp_nonce_url( add_query_arg( array(
1617
		'give-action' => 'delete_payment_note',
1618
		'note_id'     => $note->comment_ID,
1619
		'payment_id'  => $payment_id,
1620
	) ),
1621
		'give_delete_payment_note_' . $note->comment_ID
1622
	);
1623
1624
	$note_html = '<div class="give-payment-note" id="give-payment-note-' . $note->comment_ID . '">';
1625
	$note_html .= '<p>';
1626
	$note_html .= '<strong>' . $user . '</strong>&nbsp;&ndash;&nbsp;<span style="color:#aaa;font-style:italic;">' . date_i18n( $date_format, strtotime( $note->comment_date ) ) . '</span><br/>';
1627
	$note_html .= $note->comment_content;
1628
	$note_html .= '&nbsp;&ndash;&nbsp;<a href="' . esc_url( $delete_note_url ) . '" class="give-delete-payment-note" data-note-id="' . absint( $note->comment_ID ) . '" data-payment-id="' . absint( $payment_id ) . '" aria-label="' . esc_attr__( 'Delete this donation note.', 'give' ) . '">' . esc_html__( 'Delete', 'give' ) . '</a>';
1629
	$note_html .= '</p>';
1630
	$note_html .= '</div>';
1631
1632
	return $note_html;
1633
1634
}
1635
1636
/**
1637
 * Exclude notes (comments) on give_payment post type from showing in Recent
1638
 * Comments widgets
1639
 *
1640
 * @since 1.0
1641
 *
1642
 * @param object $query WordPress Comment Query Object.
1643
 *
1644
 * @return void
1645
 */
1646
function give_hide_payment_notes( $query ) {
1647
	if ( version_compare( floatval( get_bloginfo( 'version' ) ), '4.1', '>=' ) ) {
1648
		$types = isset( $query->query_vars['type__not_in'] ) ? $query->query_vars['type__not_in'] : array();
1649
		if ( ! is_array( $types ) ) {
1650
			$types = array( $types );
1651
		}
1652
		$types[]                           = 'give_payment_note';
1653
		$query->query_vars['type__not_in'] = $types;
1654
	}
1655
}
1656
1657
add_action( 'pre_get_comments', 'give_hide_payment_notes', 10 );
1658
1659
/**
1660
 * Exclude notes (comments) on give_payment post type from showing in Recent Comments widgets
1661
 *
1662
 * @since 1.0
1663
 *
1664
 * @param array  $clauses          Comment clauses for comment query.
1665
 * @param object $wp_comment_query WordPress Comment Query Object.
1666
 *
1667
 * @return array $clauses Updated comment clauses.
1668
 */
1669
function give_hide_payment_notes_pre_41( $clauses, $wp_comment_query ) {
0 ignored issues
show
Unused Code introduced by
The parameter $wp_comment_query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1670
	if ( version_compare( floatval( get_bloginfo( 'version' ) ), '4.1', '<' ) ) {
1671
		$clauses['where'] .= ' AND comment_type != "give_payment_note"';
1672
	}
1673
1674
	return $clauses;
1675
}
1676
1677
add_filter( 'comments_clauses', 'give_hide_payment_notes_pre_41', 10, 2 );
1678
1679
1680
/**
1681
 * Exclude notes (comments) on give_payment post type from showing in comment feeds
1682
 *
1683
 * @since 1.0
1684
 *
1685
 * @param string $where
1686
 * @param object $wp_comment_query WordPress Comment Query Object.
1687
 *
1688
 * @return string $where
1689
 */
1690
function give_hide_payment_notes_from_feeds( $where, $wp_comment_query ) {
0 ignored issues
show
Unused Code introduced by
The parameter $wp_comment_query is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1691
	global $wpdb;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1692
1693
	$where .= $wpdb->prepare( ' AND comment_type != %s', 'give_payment_note' );
1694
1695
	return $where;
1696
}
1697
1698
add_filter( 'comment_feed_where', 'give_hide_payment_notes_from_feeds', 10, 2 );
1699
1700
1701
/**
1702
 * Remove Give Comments from the wp_count_comments function
1703
 *
1704
 * @access public
1705
 * @since  1.0
1706
 *
1707
 * @param array $stats   (empty from core filter).
1708
 * @param int   $post_id Post ID.
1709
 *
1710
 * @return array Array of comment counts.
1711
 */
1712
function give_remove_payment_notes_in_comment_counts( $stats, $post_id ) {
1713
	global $wpdb, $pagenow;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1714
1715
	if ( 'index.php' != $pagenow ) {
1716
		return $stats;
1717
	}
1718
1719
	$post_id = (int) $post_id;
1720
1721
	if ( apply_filters( 'give_count_payment_notes_in_comments', false ) ) {
1722
		return $stats;
1723
	}
1724
1725
	$stats = wp_cache_get( "comments-{$post_id}", 'counts' );
1726
1727
	if ( false !== $stats ) {
1728
		return $stats;
1729
	}
1730
1731
	$where = 'WHERE comment_type != "give_payment_note"';
1732
1733
	if ( $post_id > 0 ) {
1734
		$where .= $wpdb->prepare( ' AND comment_post_ID = %d', $post_id );
1735
	}
1736
1737
	$count = $wpdb->get_results( "SELECT comment_approved, COUNT( * ) AS num_comments FROM {$wpdb->comments} {$where} GROUP BY comment_approved", ARRAY_A );
1738
1739
	$total    = 0;
1740
	$approved = array(
1741
		'0'            => 'moderated',
1742
		'1'            => 'approved',
1743
		'spam'         => 'spam',
1744
		'trash'        => 'trash',
1745
		'post-trashed' => 'post-trashed',
1746
	);
1747
	foreach ( (array) $count as $row ) {
1748
		// Don't count post-trashed toward totals.
1749
		if ( 'post-trashed' != $row['comment_approved'] && 'trash' != $row['comment_approved'] ) {
1750
			$total += $row['num_comments'];
1751
		}
1752
		if ( isset( $approved[ $row['comment_approved'] ] ) ) {
1753
			$stats[ $approved[ $row['comment_approved'] ] ] = $row['num_comments'];
1754
		}
1755
	}
1756
1757
	$stats['total_comments'] = $total;
1758
	foreach ( $approved as $key ) {
1759
		if ( empty( $stats[ $key ] ) ) {
1760
			$stats[ $key ] = 0;
1761
		}
1762
	}
1763
1764
	$stats = (object) $stats;
1765
	wp_cache_set( "comments-{$post_id}", $stats, 'counts' );
1766
1767
	return $stats;
1768
}
1769
1770
add_filter( 'wp_count_comments', 'give_remove_payment_notes_in_comment_counts', 10, 2 );
1771
1772
1773
/**
1774
 * Filter where older than one week
1775
 *
1776
 * @access public
1777
 * @since  1.0
1778
 *
1779
 * @param string $where Where clause.
1780
 *
1781
 * @return string $where Modified where clause.
1782
 */
1783
function give_filter_where_older_than_week( $where = '' ) {
1784
	// Payments older than one week.
1785
	$start = date( 'Y-m-d', strtotime( '-7 days' ) );
1786
	$where .= " AND post_date <= '{$start}'";
1787
1788
	return $where;
1789
}
1790
1791
1792
/**
1793
 * Get Payment Form ID.
1794
 *
1795
 * Retrieves the form title and appends the level name if present.
1796
 *
1797
 * @since 1.5
1798
 *
1799
 * @param array  $payment_meta Payment meta data.
1800
 * @param bool   $only_level   If set to true will only return the level name if multi-level enabled.
1801
 * @param string $separator    The separator between the .
1802
 *
1803
 * @return string $form_title Returns the full title if $only_level is false, otherwise returns the levels title.
1804
 */
1805
function give_get_payment_form_title( $payment_meta, $only_level = false, $separator = '' ) {
1806
1807
	$form_id    = isset( $payment_meta['form_id'] ) ? $payment_meta['form_id'] : 0;
1808
	$price_id   = isset( $payment_meta['price_id'] ) ? $payment_meta['price_id'] : null;
1809
	$form_title = isset( $payment_meta['form_title'] ) ? $payment_meta['form_title'] : '';
1810
1811
	if ( $only_level == true ) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1812
		$form_title = '';
1813
	}
1814
1815
	//If multi-level, append to the form title.
1816
	if ( give_has_variable_prices( $form_id ) ) {
1817
1818
		//Only add separator if there is a form title.
1819
		if ( ! empty( $form_title ) ) {
1820
			$form_title .= ' ' . $separator . ' ';
1821
		}
1822
1823
		$form_title .= '<span class="donation-level-text-wrap">';
1824
1825
		if ( $price_id == 'custom' ) {
1826
			$custom_amount_text = get_post_meta( $form_id, '_give_custom_amount_text', true );
1827
			$form_title         .= ! empty( $custom_amount_text ) ? $custom_amount_text : __( 'Custom Amount', 'give' );
1828
		} else {
1829
			$form_title .= give_get_price_option_name( $form_id, $price_id );
1830
		}
1831
1832
		$form_title .= '</span>';
1833
1834
	}
1835
1836
	return apply_filters( 'give_get_payment_form_title', $form_title, $payment_meta );
1837
1838
}
1839
1840
/**
1841
 * Get Price ID
1842
 *
1843
 * Retrieves the Price ID when provided a proper form ID and price (donation) total
1844
 *
1845
 * @param int    $form_id Form ID.
1846
 * @param string $price   Price ID.
1847
 *
1848
 * @return string $price_id
1849
 */
1850
function give_get_price_id( $form_id, $price ) {
1851
1852
	$price_id = 0;
1853
1854
	if ( give_has_variable_prices( $form_id ) ) {
1855
1856
		$levels = maybe_unserialize( get_post_meta( $form_id, '_give_donation_levels', true ) );
1857
1858
		foreach ( $levels as $level ) {
1859
1860
			$level_amount = (float) give_sanitize_amount( $level['_give_amount'] );
1861
1862
			// Check that this indeed the recurring price.
1863
			if ( $level_amount == $price ) {
1864
1865
				$price_id = $level['_give_id']['level_id'];
1866
1867
			}
1868
		}
1869
	}
1870
1871
	return $price_id;
1872
1873
}
1874
1875
/**
1876
 * Get/Print give form dropdown html
1877
 *
1878
 * This function is wrapper to public method forms_dropdown of Give_HTML_Elements class to get/print form dropdown html.
1879
 * Give_HTML_Elements is defined in includes/class-give-html-elements.php.
1880
 *
1881
 * @since 1.6
1882
 *
1883
 * @param array $args Arguments for form dropdown.
1884
 * @param bool  $echo This parameter decides if print form dropdown html output or not.
1885
 *
1886
 * @return string|void
1887
 */
1888
function give_get_form_dropdown( $args = array(), $echo = false ) {
1889
	$form_dropdown_html = Give()->html->forms_dropdown( $args );
1890
1891
	if ( ! $echo ) {
1892
		return $form_dropdown_html;
1893
	}
1894
1895
	echo $form_dropdown_html;
1896
}
1897
1898
/**
1899
 * Get/Print give form variable price dropdown html
1900
 *
1901
 * @since 1.6
1902
 *
1903
 * @param array $args Arguments for form dropdown.
1904
 * @param bool  $echo This parameter decide if print form dropdown html output or not.
1905
 *
1906
 * @return string|bool
0 ignored issues
show
Documentation introduced by
Should the return type not be false|string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1907
 */
1908
function give_get_form_variable_price_dropdown( $args = array(), $echo = false ) {
1909
1910
	// Check for give form id.
1911
	if ( empty( $args['id'] ) ) {
1912
		return false;
1913
	}
1914
1915
	$form = new Give_Donate_Form( $args['id'] );
1916
1917
	// Check if form has variable prices or not.
1918
	if ( ! $form->ID || ! $form->has_variable_prices() ) {
1919
		return false;
1920
	}
1921
1922
	$variable_prices        = $form->get_prices();
1923
	$variable_price_options = array();
1924
1925
	// Check if multi donation form support custom donation or not.
1926
	if ( $form->is_custom_price_mode() ) {
1927
		$variable_price_options['custom'] = _x( 'Custom', 'custom donation dropdown item', 'give' );
1928
	}
1929
1930
	// Get variable price and ID from variable price array.
1931
	foreach ( $variable_prices as $variable_price ) {
1932
		$variable_price_options[ $variable_price['_give_id']['level_id'] ] = ! empty( $variable_price['_give_text'] ) ? $variable_price['_give_text'] : give_currency_filter( give_format_amount( $variable_price['_give_amount'] ) );
1933
	}
1934
1935
	// Update options.
1936
	$args = array_merge( $args, array( 'options' => $variable_price_options ) );
1937
1938
	// Generate select html.
1939
	$form_dropdown_html = Give()->html->select( $args );
1940
1941
	if ( ! $echo ) {
1942
		return $form_dropdown_html;
1943
	}
1944
1945
	echo $form_dropdown_html;
1946
}
1947
1948
/**
1949
 * Get the price_id from the payment meta.
1950
 *
1951
 * Some gateways use `give_price_id` and others were using just `price_id`;
1952
 * This checks for the difference and falls back to retrieving it from the form as a last resort.
1953
 *
1954
 * @since 1.8.6
1955
 *
1956
 * @param $payment_meta
1957
 *
1958
 * @return string
1959
 */
1960
function give_get_payment_meta_price_id( $payment_meta ) {
1961
1962
	if(isset($payment_meta['give_price_id'])) {
1963
		$price_id =  $payment_meta['give_price_id'];
1964
	} elseif(isset($payment_meta['price_id'])) {
1965
		$price_id =  $payment_meta['price_id'];
1966
	} else {
1967
		$price_id = give_get_price_id( $payment_meta['give_form_id'], $payment_meta['price'] );
1968
	}
1969
1970
	return apply_filters('give_get_payment_meta_price_id', $price_id);
1971
1972
}