PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.16.9
GiveWP – Donation Plugin and Fundraising Platform v4.16.9
4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 2.30.0 All 255 releases
give / src / LegacySubscriptions / includes / give-subscription.php

give-subscription.php in GiveWP – Donation Plugin and Fundraising Platform 4.16.9, at src/LegacySubscriptions/includes/give-subscription.php

1,328 lines 30.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Give Recurring Subscription
4 *
5 * @package Give
6 * @copyright Copyright (c) 2017, GiveWP
7 * @license https://opensource.org/licenses/gpl-license GNU Public License
8 * @since 1.0
9 */
10
11 // Exit if accessed directly
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit;
14 }
15
16 /**
17 * Class Give_Subscription
18 *
19 * @since 4.11.0 add campaign_id
20 * @since 2.19.0 - migrated from give-recurring
21 * @since 1.0
22 */
23 class Give_Subscription {
24
25 /**
26 * @var Give_Subscriptions_DB
27 */
28 private $subs_db;
29
30 /**
31 * @var int
32 */
33 public $id = 0;
34
35 /**
36 * @var int
37 */
38 public $donor_id = 0;
39
40 /**
41 * @var string
42 */
43 public $period = '';
44
45 /**
46 * @var int
47 */
48 public $frequency = 1;
49
50 /**
51 * @var string
52 */
53 public $initial_amount = '';
54
55 /**
56 * @var string
57 */
58 public $recurring_amount = '';
59
60 /**
61 * @var float
62 */
63 public $recurring_fee_amount = 0;
64
65 /**
66 * @var int
67 */
68 public $bill_times = 0;
69
70 /**
71 * @var string
72 */
73 public $transaction_id = '';
74
75 /**
76 * @var int
77 */
78 public $parent_payment_id = 0;
79
80 /**
81 * @var int
82 */
83 public $form_id = 0;
84
85 /**
86 * @var string
87 */
88 public $created = '0000-00-00 00:00:00';
89
90 /**
91 * @var string
92 */
93 public $expiration = '0000-00-00 00:00:00';
94
95 /**
96 * @var string
97 */
98 public $status = 'pending';
99
100 /**
101 * @var string
102 */
103 public $profile_id = '';
104
105 /**
106 * @var string
107 */
108 public $gateway = '';
109
110 /**
111 * @var Give_Donor
112 */
113 public $donor;
114
115 /**
116 * @var int
117 */
118 public $campaign_id = 0;
119
120 /**
121 * @var int (backward compatibility - maps to donor_id)
122 */
123 public $customer_id = 0;
124
125 /**
126 * @var string (backward compatibility - maps to form_id)
127 */
128 public $product_id = 0;
129
130 /**
131 * @var string (subscription payment mode)
132 */
133 public $payment_mode = '';
134
135 /**
136 * @var string (subscription notes)
137 */
138 public $notes = '';
139
140 /**
141 * Give_Subscription constructor.
142 *
143 * @param int $_id_or_object Subscription ID or Object
144 * @param bool $_by_profile_id
145 */
146 function __construct( $_id_or_object = 0, $_by_profile_id = false ) {
147
148 $this->subs_db = new Give_Subscriptions_DB();
149
150 if ( $_by_profile_id ) {
151
152 $_sub = $this->subs_db->get_by( 'profile_id', $_id_or_object );
153
154 if ( empty( $_sub ) ) {
155 return false;
156 }
157
158 $_id_or_object = $_sub;
159
160 }
161
162 return $this->setup_subscription( $_id_or_object );
163 }
164
165 /**
166 * Setup the subscription object.
167 *
168 * @since 2.19.3 - cast bill_times to integer
169 *
170 * @param int $id_or_object
171 *
172 * @return Give_Subscription|bool
173 */
174 private function setup_subscription( $id_or_object = 0 ) {
175
176 if ( empty( $id_or_object ) ) {
177 return false;
178 }
179 if ( is_numeric( $id_or_object ) ) {
180
181 $sub = $this->subs_db->get( $id_or_object );
182
183 } elseif ( is_object( $id_or_object ) ) {
184
185 $sub = $id_or_object;
186
187 }
188
189 if ( empty( $sub ) ) {
190 return false;
191 }
192
193 foreach ( $sub as $key => $value ) {
194 // Backwards compatibility:
195 // Ensure product_id get sent to new form_id.
196 if ('product_id' === $key) {
197 $this->form_id = $value;
198 }
199
200 if ('customer_id' === $key) {
201 $this->donor_id = $value;
202 }
203
204 if ('bill_times' === $key) {
205 $value = (int)$value;
206 }
207
208 $this->$key = $value;
209 }
210
211 $this->donor = new Give_Donor( $this->donor_id );
212 $this->gateway = give_get_payment_gateway( $this->parent_payment_id );
213
214 do_action( 'give_recurring_setup_subscription', $this );
215
216 return $this;
217 }
218
219 /**
220 * Magic __get function to dispatch a call to retrieve a private property.
221 *
222 * @param $key
223 *
224 * @return mixed|WP_Error
225 */
226 public function __get( $key ) {
227
228 if ( method_exists( $this, 'get_' . $key ) ) {
229
230 return call_user_func( array( $this, 'get_' . $key ) );
231
232 } else {
233
234 return new WP_Error( 'give-subscription-invalid-property', sprintf( __( 'Can\'t get property %s', 'give' ), $key ) );
235
236 }
237
238 }
239
240 /**
241 * Creates a subscription.
242 *
243 * @since 4.11.0 add campaign_id
244 * @since 1.0
245 *
246 * @param array $data Array of attributes for a subscription
247 *
248 * @return mixed false if data isn't passed and class not instantiated for creation
249 */
250 public function create( $data = array() ) {
251
252 if ( $this->id != 0 ) {
253 return false;
254 }
255
256 $defaults = array(
257 'customer_id' => 0,
258 'period' => '',
259 'frequency' => 1,
260 'initial_amount' => '',
261 'recurring_amount' => '',
262 'recurring_fee_amount' => 0,
263 'bill_times' => 0,
264 'parent_payment_id' => 0,
265 'form_id' => 0,
266 'created' => '',
267 'expiration' => '',
268 'status' => '',
269 'profile_id' => '',
270 'campaign_id' => 0,
271 );
272
273 $args = wp_parse_args( $data, $defaults );
274
275 if ( $args['expiration'] && strtotime( 'NOW', current_time( 'timestamp' ) ) > strtotime( $args['expiration'], current_time( 'timestamp' ) ) ) {
276
277 if ( 'active' == $args['status'] ) {
278
279 // Force an active subscription to expired if expiration date is in the past
280 $args['status'] = 'expired';
281
282 }
283 }
284
285 do_action( 'give_subscription_pre_create', $args );
286
287 // @TODO: DB column should be updated to 'form_id' in future and remove this backwards compatibility.
288 if ( ! empty( $args['form_id'] ) ) {
289 $args['product_id'] = $args['form_id'];
290 }
291 // @TODO: DB column should be updated to 'customer_id' in future and remove this backwards compatibility.
292 if ( ! empty( $args['donor_id'] ) ) {
293 $args['customer_id'] = $args['donor_id'];
294 }
295
296 if (
297 $args['product_id']
298 && ! $args['campaign_id']
299 ) {
300 if ($campaign = give()->campaigns->getByFormId($args['product_id'])) {
301 $args['campaign_id'] = $campaign->id;
302 }
303 }
304
305 $id = $this->subs_db->create( $args );
306
307 do_action( 'give_subscription_post_create', $id, $args );
308
309 // If Payment status is renewal, then update purchase count and amount of donor.
310 $payment = new Give_Payment( $args['parent_payment_id'] );
311 if ( ( 'give_subscription' === $payment->post_status ) && ! empty( $args['customer_id'] ) ) {
312 $donor = new Give_Donor( $args['customer_id'] );
313 $donor->increase_purchase_count();
314 $donor->increase_value( $args['recurring_amount'] );
315 }
316
317 return $this->setup_subscription( $id );
318
319 }
320
321 /**
322 * Update.
323 *
324 * Updates a subscription.
325 *
326 * @param array $args Array of fields to update
327 *
328 * @return bool
329 */
330 public function update( $args ) {
331
332 $old_status = '';
333 if ( isset( $args['status'] ) && strtolower( $this->status ) !== strtolower( $args['status'] ) ) {
334 $old_status = $this->status;
335 $this->add_note( sprintf( __( 'Status changed from %s to %s', 'give' ), $this->status, $args['status'] ) );
336 }
337
338 do_action( 'give_recurring_pre_update_subscription', $this->id, $args, $this );
339
340 $ret = $this->subs_db->update( $this->id, $args );
341
342 do_action( 'give_recurring_update_subscription', $this->id, $args, $this );
343
344 if (!empty($old_status)) {
345 do_action( 'give_recurring_update_subscription_status', $this->id, $args['status'], $old_status );
346 }
347
348 return $ret;
349
350 }
351
352 /**
353 * Delete the subscription.
354 *
355 * @return bool
356 */
357 public function delete() {
358 do_action( 'give_recurring_before_delete_subscription', $this );
359 $deleted = $this->subs_db->delete( $this->id );
360 do_action( 'give_recurring_after_delete_subscription', $deleted, $this );
361
362 return $deleted;
363 }
364
365 /**
366 * Get Original Payment ID.
367 *
368 * @return int
369 */
370 public function get_original_payment_id() {
371 return $this->parent_payment_id;
372 }
373
374 /**
375 * Get Child Payments.
376 *
377 * Retrieves subscription renewal payments for a subscription.
378 *
379 * @return array
380 */
381 public function get_child_payments() {
382 $args = array(
383 'post_parent' => (int) $this->parent_payment_id,
384 'posts_per_page' => '9999',
385 'post_status' => 'any',
386 'post_type' => 'give_payment',
387 );
388
389 $cache_key = Give_Cache::get_key( 'child_payments', $args, false );
390 $payments = Give_Recurring_Cache::get_db_query( $cache_key );
391
392 if( is_null( $payments ) ) {
393 $payments = get_posts( $args );
394 Give_Recurring_Cache::set_db_query( $cache_key, $payments );
395 }
396
397 return $payments;
398 }
399
400 /**
401 * Get the Initial Payment.
402 *
403 * Retrieves the first payment object for the subscription.
404 *
405 * @return array|bool
406 */
407 public function get_initial_payment() {
408
409 $initial_payment = get_posts( array(
410 'include' => (int) $this->parent_payment_id,
411 'posts_per_page' => '1',
412 'post_status' => 'any',
413 'post_type' => 'give_payment',
414 ) );
415
416 if ( isset( $initial_payment[0] ) ) {
417 return $initial_payment[0];
418 }
419
420 return false;
421
422 }
423
424
425 /**
426 * Get Total Payments.
427 *
428 * Returns the total number of times a subscription has been paid including the initial payment (that's the +1).
429 *
430 * @return int
431 */
432 public function get_total_payments() {
433 return count( $this->get_child_payments() ) + 1;
434 }
435
436 /**
437 * Get the last payment made.
438 *
439 * Returns the subscriptions last payment made regardless of whether it was a renewal or initial payment.
440 *
441 * @since 1.8
442 *
443 * @return WP_Post object
444 */
445 public function get_last_payment() {
446
447 // If renewals, return the latest renewal.
448 $renewals = $this->get_child_payments();
449 if ( count( $renewals ) !== 0 ) {
450 return $renewals[0];
451 }
452
453 $initial_payment = get_posts( array(
454 'include' => (int) $this->parent_payment_id,
455 'posts_per_page' => '1',
456 'post_status' => 'any',
457 'post_type' => 'give_payment',
458 ) );
459
460 // If no renewals return parent payment.
461 return isset( $initial_payment[0] ) ? $initial_payment[0] : false;
462
463 }
464
465 /**
466 * Get Lifetime Value.
467 *
468 * @return int $amount
469 */
470 public function get_lifetime_value() {
471
472 $amount = 0.00;
473
474 $parent_payment = give_get_payment_by( 'id', $this->parent_payment_id );
475 $parent_payment_status = give_get_payment_status( $parent_payment );
476 $ignored_statuses = array( 'refunded', 'pending', 'abandoned', 'failed' );
477
478 if ( false === in_array( $parent_payment_status, $ignored_statuses ) ) {
479 $amount = give_donation_amount( $this->parent_payment_id );
480 }
481
482 $children = $this->get_child_payments();
483
484 if ( $children ) {
485
486 foreach ( $children as $child ) {
487 $child_payment_status = give_get_payment_status( $child );
488 if ( 'refunded' === $child_payment_status ) {
489 continue;
490 }
491
492 $amount += give_donation_amount( $child->ID );
493 }
494 }
495
496 return $amount;
497
498 }
499
500 /**
501 * Add Payment.
502 *
503 * Records a new payment on the subscription.
504 *
505 * @since 4.11.0 add campaign_id to renewal
506 * @since 2.21.3 add support for anonymous donations
507 * @since 1.12.7 Set donor first and last name in new donation
508 *
509 * @param array $args Array of values for the payment, including amount and transaction ID.
510 *
511 * @return bool
512 */
513 public function add_payment( $args = array() ) {
514
515 $args = wp_parse_args( $args, array(
516 'amount' => '',
517 'transaction_id' => '',
518 'gateway' => '',
519 'post_date' => '',
520 ) );
521
522 // Check if the payment exists.
523 if ( $this->payment_exists( $args['transaction_id'] ) ) {
524 return false;
525 }
526
527 $payment = new Give_Payment();
528 $parent = new Give_Payment( $this->parent_payment_id );
529
530 // Sanitize donation amount.
531 $args['amount'] = $this->mayBeSanitizeWebhookResponseDonationAmount( $args['amount'], $parent->currency );
532
533
534 $payment->parent_payment = $this->parent_payment_id;
535 $payment->total = $args['amount'];
536 $payment->form_title = $parent->form_title;
537 $payment->form_id = $parent->form_id;
538 $payment->customer_id = $parent->customer_id;
539 $payment->address = $parent->address;
540 $payment->first_name = $parent->user_info['first_name'];
541 $payment->last_name = $parent->user_info['last_name'];
542 $payment->user_info = $parent->user_info;
543 $payment->user_id = $parent->user_id;
544 $payment->email = $parent->email;
545 $payment->currency = $parent->currency;
546 $payment->status = 'give_subscription';
547 $payment->transaction_id = $args['transaction_id'];
548 $payment->key = $parent->key;
549 $payment->mode = $parent->mode;
550
551 $donor = new Give_Donor( $payment->customer_id );
552 $price_id = give_get_price_id( $parent->form_id, $args['amount'] );
553
554 /**
555 * Get Correct Donation Level ID for renewal amount.
556 *
557 * @param integer $price_id Price ID.
558 * @param float $amount Renewal amount
559 * @param \Give_Payment $payment Renewal Payment
560 *
561 * @return integer $price_id
562 * @since 1.8
563 *
564 */
565 $price_id = apply_filters( 'give_recurring_renewal_price_id', $price_id, $args['amount'], $parent );
566
567 // Set price id.
568 $payment->price_id = $price_id;
569
570 if ( empty( $args['gateway'] ) ) {
571 $payment->gateway = $parent->gateway;
572 } else {
573 $payment->gateway = $args['gateway'];
574 }
575
576 // If post_date is set (by synchronizer for past payments for example) then pass it along.
577 if ( ! empty( $args['post_date'] ) ) {
578 $payment->date = $args['post_date'];
579 }
580
581 if ( ! empty( $this->campaign_id ) ) {
582 $payment->campaign_id = $this->campaign_id;
583 }
584
585 // Automatically derive campaign_id from form_id if campaign exists
586 if ( empty( $payment->campaign_id ) ) {
587 $payment->campaign_id = give_derive_campaign_id_from_form_id( $payment->form_id );
588 }
589
590 // Increase the earnings for the form in the subscription.
591 give_increase_earnings( $parent->form_id, $args['amount'] );
592 // Increase the donation count for this form as well.
593 give_increase_donation_count( $parent->form_id );
594
595 $payment->add_donation( $parent->form_id, array( 'price' => $args['amount'], 'price_id' => $price_id ) );
596 $payment->save();
597
598 // Ensure campaign_id meta is saved (safety net)
599 if ( ! empty( $payment->campaign_id ) ) {
600 $payment->update_meta( '_give_campaign_id', $payment->campaign_id );
601 }
602
603 $payment->update_meta( 'subscription_id', $this->id );
604 $donor->increase_purchase_count( 1 );
605 $donor->increase_value( $args['amount'] );
606
607 if ($parent->get_meta('_give_anonymous_donation')) {
608 $payment->update_meta('_give_anonymous_donation', 1);
609 }
610
611 // Add give recurring subscription notification
612 do_action( 'give_recurring_add_subscription_payment', $payment, $this );
613 do_action( 'give_recurring_record_payment', $payment, $this->parent_payment_id, $args['amount'], $args['transaction_id'] );
614
615 return true;
616 }
617
618 /**
619 * Get Transaction ID.
620 *
621 * Retrieves the transaction ID from the subscription.
622 *
623 * @since 1.2
624 * @return bool
625 */
626 public function get_transaction_id() {
627
628 if ( empty( $this->transaction_id ) ) {
629
630 $txn_id = give_get_payment_transaction_id( $this->parent_payment_id );
631
632 if ( ! empty( $txn_id ) && (int) $this->parent_payment_id !== (int) $txn_id ) {
633 $this->set_transaction_id( $txn_id );
634 }
635 }
636
637 return $this->transaction_id;
638
639 }
640
641 /**
642 * Stores the transaction ID for the subscription donation.
643 *
644 * @since 1.2
645 *
646 * @param string $txn_id
647 *
648 * @return bool
649 */
650 public function set_transaction_id( $txn_id = '' ) {
651 $this->update( array(
652 'transaction_id' => $txn_id,
653 ) );
654 give_set_payment_transaction_id( $this->parent_payment_id, $txn_id );
655 $this->transaction_id = $txn_id;
656 }
657
658 /**
659 * Renew Payment.
660 *
661 * This method is responsible for renewing a subscription (not adding payments).
662 * It checks the expiration date, whether the subscription is active, run hooks, sets notes, and updates the
663 * subscription status as necessary. If the subscription has reached the total number of bill times the
664 * subscription will be completed.
665 *
666 * @since 1.0
667 * @return bool
668 */
669 public function renew() {
670
671 $expires = $this->get_expiration_time();
672
673 // Determine what date to use as the start for the new expiration calculation.
674 if ( $expires > current_time( 'timestamp' ) && $this->is_active() ) {
675 $base_date = $expires;
676 } else {
677 $base_date = current_time( 'timestamp' );
678 }
679
680 $last_day = cal_days_in_month( CAL_GREGORIAN, date( 'n', $base_date ), date( 'Y', $base_date ) );
681 if ( $this->period == "quarter" ) {
682 $expiration = date( 'Y-m-d H:i:s', strtotime( '+3 months 23:59:59', $base_date ) );
683 } else {
684 $expiration = date( 'Y-m-d H:i:s', strtotime( '+1 ' . $this->period . ' 23:59:59', $base_date ) );
685 }
686
687 if ( date( 'j', $base_date ) == $last_day && 'day' != $this->period ) {
688 $expiration = date( 'Y-m-d H:i:s', strtotime( $expiration . ' +2 days' ) );
689 }
690
691 $expiration = apply_filters( 'give_subscription_renewal_expiration', $expiration, $this->id, $this );
692
693 do_action( 'give_subscription_pre_renew', $this->id, $expiration, $this );
694
695 $status = 'active';
696 $times_billed = $this->get_total_payments();
697
698 // Complete subscription if applicable.
699 if ( $this->bill_times > 0 && $times_billed >= $this->bill_times ) {
700 $this->complete();
701 $status = 'completed';
702 }
703
704 $args = array(
705 'expiration' => $expiration,
706 'status' => $status,
707 );
708
709 $this->update( $args );
710
711 do_action( 'give_subscription_post_renew', $this->id, $expiration, $this );
712 do_action( 'give_recurring_set_subscription_status', $this->id, $status, $this );
713
714 }
715
716 /**
717 * Subscription Complete.
718 *
719 * Subscription is completed when the number of payments matches the billing_times field.
720 *
721 * @return void
722 */
723 public function complete() {
724
725 $args = array(
726 'status' => 'completed',
727 );
728
729 // Prevent duplicate update.
730 if( ! $this->can_update_status('completed') ) {
731 return;
732 }
733
734 if ( $this->subs_db->update( $this->id, $args ) ) {
735
736 do_action( 'give_subscription_completed', $this->id, $this );
737
738 }
739
740 }
741
742 /**
743 * Subscription Expire.
744 *
745 * Marks a subscription as expired. Subscription is completed when the billing times is reached.
746 *
747 * @since 1.1.2
748 * @return void
749 */
750 public function expire() {
751
752 $args = array(
753 'status' => 'expired',
754 );
755
756 // Prevent duplicate update.
757 if( ! $this->can_update_status('expired') ) {
758 return;
759 }
760
761 if ( $this->subs_db->update( $this->id, $args ) ) {
762
763 do_action( 'give_subscription_expired', $this->id, $this );
764
765 }
766
767 $this->status = 'expired';
768
769 }
770
771 /**
772 * Marks a subscription as failing.
773 *
774 * @since 1.1.2
775 * @return void
776 */
777 public function failing() {
778
779 $args = array(
780 'status' => 'failing',
781 );
782
783 // Prevent duplicate update.
784 if( ! $this->can_update_status('failing') ) {
785 return;
786 }
787
788 if ( $this->subs_db->update( $this->id, $args ) ) {
789 do_action( 'give_subscription_failing', $this->id, $this );
790 }
791
792 $this->status = 'failing';
793
794 }
795
796 /**
797 * Subscription Cancelled.
798 *
799 * Marks a subscription as cancelled.
800 *
801 * @return void
802 */
803 public function cancel() {
804
805 $args = array(
806 'status' => 'cancelled',
807 );
808
809 // Prevent duplicate update.
810 if( ! $this->can_update_status('cancelled') ) {
811 return;
812 }
813
814 if ( $this->subs_db->update( $this->id, $args ) ) {
815
816 if ( is_user_logged_in() ) {
817
818 $userdata = get_userdata( get_current_user_id() );
819 $user = $userdata->user_login;
820
821 } else {
822
823 $user = __( 'gateway', 'give' );
824
825 }
826
827 $note = sprintf( __( 'Subscription #%1$d cancelled by %2$s', 'give' ), $this->id, $user );
828 $this->donor->add_note( $note );
829 $this->status = 'cancelled';
830
831 // Add give subscription cancelled notification
832 do_action( 'give_subscription_cancelled', $this->id, $this );
833
834 }
835
836 }
837
838 /**
839 * Can Cancel.
840 *
841 * This method is filtered by payment gateways in order to return true on subscriptions
842 * that can be cancelled with a profile ID through the merchant processor.
843 *
844 * @return mixed
845 */
846 public function can_cancel() {
847 return apply_filters( 'give_subscription_can_cancel', false, $this );
848 }
849
850 /**
851 * Can Pause.
852 *
853 * This method is filtered by payment gateways in order to return true on subscriptions
854 * that can be paused with a profile ID through the merchant processor.
855 *
856 * @return mixed
857 */
858 public function can_pause()
859 {
860 return apply_filters('give_subscription_can_pause', false, $this);
861 }
862
863 /**
864 * Can Sync.
865 *
866 * This method is filtered by payment gateways in order to return true on subscriptions
867 * that can sync through the merchant processor.
868 *
869 * @return mixed
870 */
871 public function can_sync() {
872 return apply_filters( 'give_subscription_can_sync', false, $this );
873 }
874
875 /**
876 * Get Cancel URL.
877 *
878 * @return mixed
879 */
880 public function get_cancel_url() {
881
882 $url = wp_nonce_url( add_query_arg( array(
883 'give_action' => 'cancel_subscription',
884 'sub_id' => $this->id,
885 ) ), "give-recurring-cancel-{$this->id}" );
886
887 return apply_filters( 'give_subscription_cancel_url', esc_url($url), $this );
888 }
889
890
891 /**
892 * Can Update.
893 *
894 * @since 1.1.2
895 * @return mixed
896 */
897 public function can_update() {
898 return apply_filters( 'give_subscription_can_update', false, $this );
899 }
900
901 /**
902 * Can Update Subscription.
903 *
904 * @since 1.8
905 * @return mixed
906 */
907 public function can_update_subscription() {
908 return apply_filters( 'give_subscription_can_update_subscription', false, $this );
909 }
910
911 /**
912 * Get Update URL.
913 *
914 * Retrieves the URL to update subscription.
915 *
916 * @since 1.1.2
917 * @return string $url
918 */
919 public function get_update_url() {
920
921 $url = esc_url(add_query_arg( array(
922 'action' => 'update',
923 'subscription_id' => $this->id,
924 ) ) );
925
926 return apply_filters( 'give_subscription_update_url', $url, $this );
927 }
928
929 /**
930 * Get Edit Subscription URL
931 *
932 * @since 1.8
933 * @return string
934 */
935 public function get_edit_subscription_url() {
936
937 $url = esc_url(add_query_arg( array(
938 'action' => 'edit_subscription',
939 'subscription_id' => $this->id,
940 ), give_get_subscriptions_page_uri() ));
941
942 return apply_filters( 'give_subscription_edit_subscription_url', $url, $this );
943 }
944
945 /**
946 * Is Active.
947 *
948 * @return bool $ret Whether the subscription is active or not.
949 */
950 public function is_active() {
951
952 $ret = false;
953
954 if ( ! $this->is_expired() && ( $this->status == 'active' || $this->status == 'cancelled' ) ) {
955 $ret = true;
956 }
957
958 return apply_filters( 'give_subscription_is_active', $ret, $this->id, $this );
959
960 }
961
962 /**
963 * Is Complete.
964 *
965 * @return bool $ret Whether the subscription is complete or not.
966 */
967 public function is_complete() {
968
969 $ret = false;
970
971 if ( 'completed' === $this->status ) {
972 $ret = true;
973 }
974
975 return apply_filters( 'give_subscription_is_complete', $ret, $this->id, $this );
976
977 }
978
979 /**
980 * Is Paused.
981 *
982 * @return bool $ret Whether the subscription is paused or not.
983 */
984 public function is_paused()
985 {
986 $ret = false;
987
988 if ('paused' === $this->status) {
989 $ret = true;
990 }
991
992 return apply_filters('give_subscription_is_paused', $ret, $this->id, $this);
993 }
994
995
996 /**
997 * Is Expired.
998 *
999 * @return bool|string
1000 */
1001 public function is_expired() {
1002
1003 $ret = false;
1004
1005 if ( 'expired' === $this->status ) {
1006 $ret = true;
1007 }
1008
1009 return apply_filters( 'give_subscription_is_expired', $ret, $this->id, $this );
1010
1011 }
1012
1013 /**
1014 * Retrieves the expiration date.
1015 *
1016 * @return string
1017 */
1018 public function get_expiration() {
1019 return $this->expiration;
1020 }
1021
1022 /**
1023 * Get Expiration Time.
1024 *
1025 * Retrieves the expiration date in a timestamp.
1026 *
1027 * @return int
1028 */
1029 public function get_expiration_time() {
1030 return strtotime( $this->expiration, current_time( 'timestamp' ) );
1031 }
1032
1033 /**
1034 * Retrieves the subscription status.
1035 *
1036 * @return int
1037 */
1038 public function get_status() {
1039 return $this->status;
1040 }
1041
1042 /**
1043 * Get Subscription Progress.
1044 *
1045 * Returns the subscription progress compared to `bill_times` such as "1/3" or "1/ Ongoing".
1046 *
1047 * @return int
1048 */
1049 public function get_subscription_progress() {
1050 return sprintf(
1051 '%1$s / %2$s',
1052 $this->get_total_payments(),
1053 0 === intval( $this->bill_times ) ? __( 'Ongoing', 'give' ) : $this->bill_times
1054 );
1055 }
1056
1057 /**
1058 * Get Subscription End Date.
1059 *
1060 * @return int
1061 */
1062 public function get_subscription_end_time() {
1063
1064 $bill_times = intval( $this->bill_times );
1065
1066 // Date out = the end of the subscription.
1067 // Subtract 1 due to initial donation being counted.
1068 $date_out = '+' . ( $bill_times - 1 ) . ' ' . $this->period;
1069
1070 return strtotime( $date_out, strtotime( $this->created ) );
1071
1072 }
1073
1074 /**
1075 * Get the Subscription Renewal Date.
1076 *
1077 * @param bool $localized Flag to return date in localized format or not
1078 *
1079 * @return string
1080 */
1081 public function get_renewal_date( $localized = true ) {
1082
1083 $expires = $this->get_expiration_time();
1084 $frequency = ! empty( $this->frequency ) ? intval( $this->frequency ) : 1;
1085
1086 // If renewal date is already in the future it's set so return it.
1087 if ($expires > current_time('timestamp') && ($this->is_active() || $this->is_paused())) {
1088 return $localized
1089 ? date_i18n( give_date_format(), strtotime( $this->expiration ) )
1090 : date( 'Y-m-d H:i:s', strtotime( $this->expiration ) );
1091 }
1092
1093 $last_payment = $this->get_last_payment();
1094
1095 // The renewal date is in the past, recalculate it based off last payment made on subscription.
1096 // Fallback to current time if last payment returns nothing to prevent PHP notice and 1970 date.
1097 $last_payment_timestamp = isset( $last_payment->post_date ) ? strtotime( $last_payment->post_date ) : current_time( 'timestamp' );
1098 $renewal_timestamp = strtotime( '+ ' . $frequency . $this->period . ' 23:59:59', $last_payment_timestamp );
1099
1100 return $localized
1101 ? date_i18n( give_date_format(), $renewal_timestamp )
1102 : date( 'Y-m-d H:i:s', $renewal_timestamp );
1103
1104 }
1105
1106
1107 /**
1108 * Is Parent Payment.
1109 *
1110 * @since 1.2
1111 *
1112 * @param int $donation_id Donation ID.
1113 *
1114 * @return bool
1115 */
1116 public function is_parent_payment( $donation_id ) {
1117 return give_recurring_is_parent_donation( $donation_id );
1118 }
1119
1120 /**
1121 * Payment Exists.
1122 *
1123 * @param string $txn_id transaction ID.
1124 *
1125 * @return bool
1126 */
1127 public function payment_exists( $txn_id = '' ) {
1128 global $wpdb;
1129
1130 if ( empty( $txn_id ) ) {
1131 return false;
1132 }
1133
1134 $txn_id = esc_sql( $txn_id );
1135
1136 $donation_meta_table_name = Give()->payment_meta->table_name;
1137 $donation_id_col_name = Give()->payment_meta->get_meta_type() . '_id';
1138
1139 $donation = $wpdb->get_var(
1140 "
1141 SELECT {$donation_id_col_name}
1142 FROM {$donation_meta_table_name}
1143 WHERE meta_key = '_give_payment_transaction_id'
1144 AND meta_value = '{$txn_id}'
1145 LIMIT 1
1146 "
1147 );
1148
1149 if ( $donation != null ) {
1150 return true;
1151 }
1152
1153 return false;
1154 }
1155
1156 /**
1157 * Get the parsed notes for a subscription as an array
1158 *
1159 * @since 1.4
1160 *
1161 * @param integer $length The number of notes to get
1162 * @param integer $paged What note to start at
1163 *
1164 * @return array The notes requested
1165 */
1166 public function get_notes( $length = 20, $paged = 1 ) {
1167
1168 $length = is_numeric( $length ) ? $length : 20;
1169 $offset = is_numeric( $paged ) && $paged != 1 ? ( ( absint( $paged ) - 1 ) * $length ) : 0;
1170
1171 $all_notes = $this->get_raw_notes();
1172 $notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
1173
1174 $desired_notes = array_slice( $notes_array, $offset, $length );
1175
1176 return $desired_notes;
1177
1178 }
1179
1180 /**
1181 * Get the total number of notes we have after parsing
1182 *
1183 * @since 1.4
1184 * @return int The number of notes for the subscription
1185 */
1186 public function get_notes_count() {
1187
1188 $all_notes = $this->get_raw_notes();
1189 $notes_array = array_reverse( array_filter( explode( "\n\n", $all_notes ) ) );
1190
1191 return count( $notes_array );
1192
1193 }
1194
1195
1196 /**
1197 * Add a note for the subscription
1198 *
1199 * @since 1.4
1200 *
1201 * @param string $note The note to add
1202 *
1203 * @return string|boolean The new note if added successfully, false otherwise.
1204 */
1205 public function add_note( $note = '' ) {
1206
1207 $note = trim( $note );
1208 if ( empty( $note ) ) {
1209 return false;
1210 }
1211
1212 $notes = $this->get_raw_notes();
1213
1214 if ( empty( $notes ) ) {
1215 $notes = '';
1216 }
1217
1218 $note_string = date_i18n( 'F j, Y H:i:s', current_time( 'timestamp' ) ) . ' - ' . $note;
1219 $new_note = apply_filters( 'give_subscription_add_note_string', $note_string );
1220 $notes .= "\n\n" . $new_note;
1221
1222 do_action( 'give_subscription_pre_add_note', $new_note, $this->id );
1223
1224 $updated = $this->update( array( 'notes' => $notes ) );
1225
1226 do_action( 'give_subscription_post_add_note', $this->get_notes(), $new_note, $this->id );
1227
1228 // Return the formatted note, so we can test, as well as update any displays
1229 return $new_note;
1230
1231 }
1232
1233 /**
1234 * Get the notes column for the subscription.
1235 *
1236 * @since 1.4
1237 * @return string The Notes for the subscription, non-parsed
1238 */
1239 private function get_raw_notes() {
1240
1241 $all_notes = $this->subs_db->get_column( 'notes', $this->id );
1242
1243 return (string) $all_notes;
1244
1245 }
1246
1247 /**
1248 * Convert object to array
1249 *
1250 * @since 1.4
1251 *
1252 * @return array
1253 */
1254 public function to_array() {
1255
1256 $array = array();
1257 foreach ( get_object_vars( $this ) as $prop => $var ) {
1258
1259 if ( is_object( $var ) && is_callable( array( $var, 'to_array' ) ) ) {
1260
1261 $array[ get_class( $var ) ] = $var->to_array();
1262
1263 } else {
1264
1265 $array[ $prop ] = $var;
1266
1267 }
1268 }
1269
1270 return $array;
1271 }
1272
1273 /**
1274 * Check if we can update subscription status or not
1275 *
1276 * It will help to prevent duplicate updates
1277 *
1278 * @param string $status
1279 *
1280 * @return bool
1281 * @since 1.9.8
1282 *
1283 */
1284 private function can_update_status( $status = '' ) {
1285 return $status && ( $status !== $this->subs_db->get_column( 'status', $this->id ) );
1286 }
1287
1288 /**
1289 * Return sanitized donation amount which comes from webhook and formatted with standard formatting setting. (number of decimal: "2", decimal separator: "." ).
1290 *
1291 * @param float $donationAmount
1292 * @param string $currencyCode
1293 *
1294 * @return float
1295 * @since 1.10.5
1296 */
1297 private function mayBeSanitizeWebhookResponseDonationAmount( $donationAmount, $currencyCode ) {
1298 // Is processing webhook for any payment gateway.
1299 if ( empty( $_GET['give-listener'] ) ) {
1300 return $donationAmount;
1301 }
1302
1303 $donationAmountStr = (string) $donationAmount;
1304 $numberOfDecimal = give_get_price_decimals( $currencyCode );
1305 $thousandSeparator = give_get_price_thousand_separator( $currencyCode );
1306 $decimalSeparator = give_get_price_decimal_separator( $currencyCode );
1307 $amountPart = explode( '.', $donationAmountStr );
1308
1309 // Sanitize donation amount only if
1310 // 1. number of decimal is set to zero for give currency.
1311 // 2. "." is thousand separator.
1312 // 3. amount formatted with ".".
1313 // 4. number of decimal for amount is two. like 10.25 or 40.00
1314 if (
1315 ! $numberOfDecimal &&
1316 '.' === $thousandSeparator &&
1317 false !== strpos( $donationAmountStr, $thousandSeparator ) &&
1318 false === strpos( $donationAmountStr, $decimalSeparator ) &&
1319 2 === count( $amountPart ) &&
1320 2 === strlen( $amountPart[1] )
1321 ) {
1322 $donationAmount = number_format( (float) $donationAmount, 10 );
1323 }
1324
1325 return $donationAmount;
1326 }
1327 }
1328