Passed
Pull Request — master (#404)
by Brian
05:01
created

WPInv_Subscription::get_subscription_id_by_field()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 46
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 22
c 1
b 0
f 0
dl 0
loc 46
rs 8.6346
cc 7
nc 9
nop 2
1
<?php
2
/**
3
 * Contains the subscription class.
4
 *
5
 * @since 1.0.19
6
 * @package Invoicing
7
 */
8
9
defined( 'ABSPATH' ) || exit;
10
11
/**
12
 * The Subscription Class
13
 *
14
 * @since  1.0.0
15
 */
16
class WPInv_Subscription extends GetPaid_Data {
17
18
	/**
19
	 * Which data store to load.
20
	 *
21
	 * @var string
22
	 */
23
	protected $data_store_name = 'subscription';
24
25
	/**
26
	 * This is the name of this object type.
27
	 *
28
	 * @var string
29
	 */
30
	protected $object_type = 'subscription';
31
32
	/**
33
	 * Item Data array. This is the core item data exposed in APIs.
34
	 *
35
	 * @since 1.0.19
36
	 * @var array
37
	 */
38
	protected $data = array(
39
		'customer_id'       => 0,
40
		'frequency'         => 1,
41
		'period'            => 'D',
42
		'initial_amount'    => null,
43
		'recurring_amount'  => null,
44
		'bill_times'        => 0,
45
		'transaction_id'    => '',
46
		'parent_payment_id' => null,
47
		'product_id'        => 0,
48
		'created'           => '0000-00-00 00:00:00',
49
		'expiration'        => '0000-00-00 00:00:00',
50
		'trial_period'      => null,
51
		'status'            => 'pending',
52
		'profile_id'        => '',
53
		'gateway'           => '',
54
		'customer'          => '',
55
	);
56
57
	/**
58
	 * Stores the status transition information.
59
	 *
60
	 * @since 1.0.19
61
	 * @var bool
62
	 */
63
	protected $status_transition = false;
64
65
	private $subs_db;
0 ignored issues
show
introduced by
The private property $subs_db is not used, and could be removed.
Loading history...
66
67
	/**
68
	 * Get the subscription if ID is passed, otherwise the subscription is new and empty.
69
	 *
70
	 * @param  int|string|object|WPInv_Subscription $subscription Subscription id, profile_id, or object to read.
71
	 * @param  bool $deprecated
72
	 */
73
	function __construct( $subscription = 0, $deprecated = false ) {
74
75
		parent::__construct( $subscription );
0 ignored issues
show
Bug introduced by
It seems like $subscription can also be of type string; however, parameter $read of GetPaid_Data::__construct() does only seem to accept array|integer|object, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

75
		parent::__construct( /** @scrutinizer ignore-type */ $subscription );
Loading history...
76
77
		if ( ! $deprecated && ! empty( $subscription ) && is_numeric( $subscription ) ) {
78
			$this->set_id( $subscription );
0 ignored issues
show
Bug introduced by
It seems like $subscription can also be of type string; however, parameter $id of GetPaid_Data::set_id() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

78
			$this->set_id( /** @scrutinizer ignore-type */ $subscription );
Loading history...
79
		} elseif ( $subscription instanceof self ) {
80
			$this->set_id( $subscription->get_id() );
81
		} elseif ( ! empty( $subscription->id ) ) {
82
			$this->set_id( $subscription->id );
83
		} elseif ( $deprecated && $subscription_id = self::get_subscription_id_by_field( $subscription, 'profile_id' ) ) {
84
			$this->set_id( $subscription_id );
85
		} else {
86
			$this->set_object_read( true );
87
		}
88
89
		// Load the datastore.
90
		$this->data_store = GetPaid_Data_Store::load( $this->data_store_name );
91
92
		if ( $this->get_id() > 0 ) {
93
			$this->data_store->read( $this );
94
		}
95
96
	}
97
98
	/**
99
	 * Given an invoice id, profile id, transaction id, it returns the subscription's id.
100
	 *
101
	 *
102
	 * @static
103
	 * @param string $value
104
	 * @param string $field Either invoice_id, transaction_id or profile_id.
105
	 * @since 1.0.19
106
	 * @return int
107
	 */
108
	public static function get_subscription_id_by_field( $value, $field = 'profile_id' ) {
109
        global $wpdb;
110
111
		// Trim the value.
112
		$value = trim( $value );
113
114
		if ( empty( $value ) ) {
115
			return 0;
116
		}
117
118
		if ( 'invoice_id' == $field ) {
119
			$field = 'parent_payment_id';
120
		}
121
122
        // Valid fields.
123
        $fields = array(
124
			'parent_payment_id',
125
			'transaction_id',
126
			'profile_id'
127
		);
128
129
		// Ensure a field has been passed.
130
		if ( empty( $field ) || ! in_array( $field, $fields ) ) {
131
			return 0;
132
		}
133
134
		// Maybe retrieve from the cache.
135
		$subscription_id   = wp_cache_get( $value, "getpaid_subscription_{$field}s_to_subscription_ids" );
136
		if ( ! empty( $subscription_id ) ) {
137
			return $subscription_id;
138
		}
139
140
        // Fetch from the db.
141
        $table            = $wpdb->prefix . 'wpinv_subscriptions';
142
        $subscription_id  = (int) $wpdb->get_var(
143
            $wpdb->prepare( "SELECT `id` FROM $table WHERE `$field`=%s LIMIT 1", $value )
144
        );
145
146
		if ( empty( $subscription_id ) ) {
147
			return 0;
148
		}
149
150
		// Update the cache with our data.
151
		wp_cache_set( $value, $subscription_id, "getpaid_subscription_{$field}s_to_subscription_ids" );
152
153
		return $subscription_id;
154
	}
155
156
	/**
157
     * Clears the subscription's cache.
158
     */
159
    public function clear_cache() {
160
		wp_cache_delete( $this->get_parent_payment_id(), 'getpaid_subscription_parent_payment_ids_to_subscription_ids' );
161
		wp_cache_delete( $this->get_transaction_id(), 'getpaid_subscription_transaction_ids_to_subscription_ids' );
162
		wp_cache_delete( $this->get_profile_id(), 'getpaid_subscription_profile_ids_to_subscription_ids' );
163
		wp_cache_delete( $this->get_id(), 'getpaid_subscriptions' );
164
	}
165
166
	/**
167
     * Checks if a subscription key is set.
168
     */
169
    public function _isset( $key ) {
170
        return isset( $this->data[$key] ) || method_exists( $this, "get_$key" );
171
	}
172
173
	/*
174
	|--------------------------------------------------------------------------
175
	| CRUD methods
176
	|--------------------------------------------------------------------------
177
	|
178
	| Methods which create, read, update and delete subscriptions from the database.
179
	|
180
    */
181
182
	/*
183
	|--------------------------------------------------------------------------
184
	| Getters
185
	|--------------------------------------------------------------------------
186
	*/
187
188
	/**
189
	 * Get customer id.
190
	 *
191
	 * @since 1.0.19
192
	 * @param  string $context View or edit context.
193
	 * @return int
194
	 */
195
	public function get_customer_id( $context = 'view' ) {
196
		return (int) $this->get_prop( 'customer_id', $context );
197
	}
198
199
	/**
200
	 * Get customer information.
201
	 *
202
	 * @since 1.0.19
203
	 * @param  string $context View or edit context.
204
	 * @return WP_User|false WP_User object on success, false on failure.
205
	 */
206
	public function get_customer( $context = 'view' ) {
207
		return get_userdata( $this->get_customer_id( $context ) );
208
	}
209
210
	/**
211
	 * Get parent invoice id.
212
	 *
213
	 * @since 1.0.19
214
	 * @param  string $context View or edit context.
215
	 * @return int
216
	 */
217
	public function get_parent_invoice_id( $context = 'view' ) {
218
		return (int) $this->get_prop( 'parent_payment_id', $context );
219
	}
220
221
	/**
222
	 * Alias for self::get_parent_invoice_id().
223
	 *
224
	 * @since 1.0.19
225
	 * @param  string $context View or edit context.
226
	 * @return int
227
	 */
228
    public function get_parent_payment_id( $context = 'view' ) {
229
        return $this->get_parent_invoice_id( $context );
230
	}
231
232
	/**
233
     * Alias for self::get_parent_invoice_id().
234
     *
235
     * @since  1.0.0
236
     * @return int
237
     */
238
    public function get_original_payment_id( $context = 'view' ) {
239
        return $this->get_parent_invoice_id( $context );
240
    }
241
242
	/**
243
	 * Get parent invoice.
244
	 *
245
	 * @since 1.0.19
246
	 * @param  string $context View or edit context.
247
	 * @return WPInv_Invoice
248
	 */
249
	public function get_parent_invoice( $context = 'view' ) {
250
		return new WPInv_Invoice( $this->get_parent_invoice_id( $context ) );
251
	}
252
253
	/**
254
	 * Alias for self::get_parent_invoice().
255
	 *
256
	 * @since 1.0.19
257
	 * @param  string $context View or edit context.
258
	 * @return WPInv_Invoice
259
	 */
260
    public function get_parent_payment( $context = 'view' ) {
261
        return $this->get_parent_invoice( $context );
262
	}
263
264
	/**
265
	 * Get subscription's product id.
266
	 *
267
	 * @since 1.0.19
268
	 * @param  string $context View or edit context.
269
	 * @return int
270
	 */
271
	public function get_product_id( $context = 'view' ) {
272
		return (int) $this->get_prop( 'product_id', $context );
273
	}
274
275
	/**
276
	 * Get the subscription product.
277
	 *
278
	 * @since 1.0.19
279
	 * @param  string $context View or edit context.
280
	 * @return WPInv_Item
281
	 */
282
	public function get_product( $context = 'view' ) {
283
		return new WPInv_Item( $this->get_product_id( $context ) );
284
	}
285
286
	/**
287
	 * Get parent invoice's gateway.
288
	 *
289
	 * Here for backwards compatibility.
290
	 *
291
	 * @since 1.0.19
292
	 * @param  string $context View or edit context.
293
	 * @return string
294
	 */
295
	public function get_gateway( $context = 'view' ) {
296
		return $this->get_parent_invoice( $context )->get_gateway();
297
	}
298
299
	/**
300
	 * Get the period of a renewal.
301
	 *
302
	 * @since 1.0.19
303
	 * @param  string $context View or edit context.
304
	 * @return string
305
	 */
306
	public function get_period( $context = 'view' ) {
307
		return $this->get_prop( 'period', $context );
308
	}
309
310
	/**
311
	 * Get number of periods each renewal is valid for.
312
	 *
313
	 * @since 1.0.19
314
	 * @param  string $context View or edit context.
315
	 * @return int
316
	 */
317
	public function get_frequency( $context = 'view' ) {
318
		return (int) $this->get_prop( 'frequency', $context );
319
	}
320
321
	/**
322
	 * Get the initial amount for the subscription.
323
	 *
324
	 * @since 1.0.19
325
	 * @param  string $context View or edit context.
326
	 * @return float
327
	 */
328
	public function get_initial_amount( $context = 'view' ) {
329
		return (float) wpinv_sanitize_amount( $this->get_prop( 'initial_amount', $context ) );
330
	}
331
332
	/**
333
	 * Get the recurring amount for the subscription.
334
	 *
335
	 * @since 1.0.19
336
	 * @param  string $context View or edit context.
337
	 * @return float
338
	 */
339
	public function get_recurring_amount( $context = 'view' ) {
340
		return (float) wpinv_sanitize_amount( $this->get_prop( 'recurring_amount', $context ) );
341
	}
342
343
	/**
344
	 * Get number of times that this subscription can be renewed.
345
	 *
346
	 * @since 1.0.19
347
	 * @param  string $context View or edit context.
348
	 * @return int
349
	 */
350
	public function get_bill_times( $context = 'view' ) {
351
		return (int) $this->get_prop( 'bill_times', $context );
352
	}
353
354
	/**
355
	 * Get transaction id of this subscription's parent invoice.
356
	 *
357
	 * @since 1.0.19
358
	 * @param  string $context View or edit context.
359
	 * @return string
360
	 */
361
	public function get_transaction_id( $context = 'view' ) {
362
		return $this->get_prop( 'transaction_id', $context );
363
	}
364
365
	/**
366
	 * Get the date that the subscription was created.
367
	 *
368
	 * @since 1.0.19
369
	 * @param  string $context View or edit context.
370
	 * @return string
371
	 */
372
	public function get_created( $context = 'view' ) {
373
		return $this->get_prop( 'created', $context );
374
	}
375
376
	/**
377
	 * Alias for self::get_created().
378
	 *
379
	 * @since 1.0.19
380
	 * @param  string $context View or edit context.
381
	 * @return string
382
	 */
383
	public function get_date_created( $context = 'view' ) {
384
		return $this->get_created( $context );
385
	}
386
387
	/**
388
	 * Retrieves the creation date in a timestamp
389
	 *
390
	 * @since  1.0.0
391
	 * @return int
392
	 */
393
	public function get_time_created() {
394
		$created = $this->get_date_created();
395
		return empty( $created ) ? current_time( 'timestamp' ) : strtotime( $created, current_time( 'timestamp' ) );
396
	}
397
398
	/**
399
	 * Get GMT date when the subscription was created.
400
	 *
401
	 * @since 1.0.19
402
	 * @param  string $context View or edit context.
403
	 * @return string
404
	 */
405
	public function get_date_created_gmt( $context = 'view' ) {
406
        $date = $this->get_date_created( $context );
407
408
        if ( $date ) {
409
            $date = get_gmt_from_date( $date );
410
        }
411
		return $date;
412
	}
413
414
	/**
415
	 * Get the date that the subscription will renew.
416
	 *
417
	 * @since 1.0.19
418
	 * @param  string $context View or edit context.
419
	 * @return string
420
	 */
421
	public function get_next_renewal_date( $context = 'view' ) {
422
		return $this->get_prop( 'expiration', $context );
423
	}
424
425
	/**
426
	 * Alias for self::get_next_renewal_date().
427
	 *
428
	 * @since 1.0.19
429
	 * @param  string $context View or edit context.
430
	 * @return string
431
	 */
432
	public function get_expiration( $context = 'view' ) {
433
		return $this->get_next_renewal_date( $context );
434
	}
435
436
	/**
437
	 * Retrieves the expiration date in a timestamp
438
	 *
439
	 * @since  1.0.0
440
	 * @return int
441
	 */
442
	public function get_expiration_time() {
443
		$expiration = $this->get_expiration();
444
445
		if ( empty( $expiration ) || '0000-00-00 00:00:00' == $expiration ) {
446
			return current_time( 'timestamp' );
447
		}
448
449
		$expiration = strtotime( $expiration, current_time( 'timestamp' ) );
450
		return $expiration < current_time( 'timestamp' ) ? current_time( 'timestamp' ) : $expiration;
451
	}
452
453
	/**
454
	 * Get GMT date when the subscription will renew.
455
	 *
456
	 * @since 1.0.19
457
	 * @param  string $context View or edit context.
458
	 * @return string
459
	 */
460
	public function get_next_renewal_date_gmt( $context = 'view' ) {
461
        $date = $this->get_next_renewal_date( $context );
462
463
        if ( $date ) {
464
            $date = get_gmt_from_date( $date );
465
        }
466
		return $date;
467
	}
468
469
	/**
470
	 * Get the subscription's trial period.
471
	 *
472
	 * @since 1.0.19
473
	 * @param  string $context View or edit context.
474
	 * @return string
475
	 */
476
	public function get_trial_period( $context = 'view' ) {
477
		return $this->get_prop( 'trial_period', $context );
478
	}
479
480
	/**
481
	 * Get the subscription's status.
482
	 *
483
	 * @since 1.0.19
484
	 * @param  string $context View or edit context.
485
	 * @return string
486
	 */
487
	public function get_status( $context = 'view' ) {
488
		return $this->get_prop( 'status', $context );
489
	}
490
491
	/**
492
	 * Get the subscription's profile id.
493
	 *
494
	 * @since 1.0.19
495
	 * @param  string $context View or edit context.
496
	 * @return string
497
	 */
498
	public function get_profile_id( $context = 'view' ) {
499
		return $this->get_prop( 'profile_id', $context );
500
	}
501
502
	/*
503
	|--------------------------------------------------------------------------
504
	| Setters
505
	|--------------------------------------------------------------------------
506
	*/
507
508
	/**
509
	 * Set customer id.
510
	 *
511
	 * @since 1.0.19
512
	 * @param  int $value The customer's id.
513
	 */
514
	public function set_customer_id( $value ) {
515
		$this->set_prop( 'customer_id', (int) $value );
516
	}
517
518
	/**
519
	 * Set parent invoice id.
520
	 *
521
	 * @since 1.0.19
522
	 * @param  int $value The parent invoice id.
523
	 */
524
	public function set_parent_invoice_id( $value ) {
525
		$this->set_prop( 'parent_payment_id', (int) $value );
526
	}
527
528
	/**
529
	 * Alias for self::set_parent_invoice_id().
530
	 *
531
	 * @since 1.0.19
532
	 * @param  int $value The parent invoice id.
533
	 */
534
    public function set_parent_payment_id( $value ) {
535
        $this->set_parent_invoice_id( $value );
536
	}
537
538
	/**
539
     * Alias for self::set_parent_invoice_id().
540
     *
541
     * @since 1.0.19
542
	 * @param  int $value The parent invoice id.
543
     */
544
    public function set_original_payment_id( $value ) {
545
        $this->set_parent_invoice_id( $value );
546
	}
547
548
	/**
549
	 * Set subscription's product id.
550
	 *
551
	 * @since 1.0.19
552
	 * @param  int $value The subscription product id.
553
	 */
554
	public function set_product_id( $value ) {
555
		$this->set_prop( 'product_id', (int) $value );
556
	}
557
558
	/**
559
	 * Set the period of a renewal.
560
	 *
561
	 * @since 1.0.19
562
	 * @param  string $value The renewal period.
563
	 */
564
	public function set_period( $value ) {
565
		$this->set_prop( 'period', $value );
566
	}
567
568
	/**
569
	 * Set number of periods each renewal is valid for.
570
	 *
571
	 * @since 1.0.19
572
	 * @param  int $value The subscription frequency.
573
	 */
574
	public function set_frequency( $value ) {
575
		$value = empty( $value ) ? 1 : (int) $value;
576
		$this->set_prop( 'frequency', absint( $value ) );
577
	}
578
579
	/**
580
	 * Set the initial amount for the subscription.
581
	 *
582
	 * @since 1.0.19
583
	 * @param  float $value The initial subcription amount.
584
	 */
585
	public function set_initial_amount( $value ) {
586
		$this->set_prop( 'initial_amount', wpinv_sanitize_amount( $value ) );
587
	}
588
589
	/**
590
	 * Set the recurring amount for the subscription.
591
	 *
592
	 * @since 1.0.19
593
	 * @param  float $value The recurring subcription amount.
594
	 */
595
	public function set_recurring_amount( $value ) {
596
		$this->set_prop( 'recurring_amount', wpinv_sanitize_amount( $value ) );
597
	}
598
599
	/**
600
	 * Set number of times that this subscription can be renewed.
601
	 *
602
	 * @since 1.0.19
603
	 * @param  int $value Bill times.
604
	 */
605
	public function set_bill_times( $value ) {
606
		$this->set_prop( 'bill_times', (int) $value );
607
	}
608
609
	/**
610
	 * Get transaction id of this subscription's parent invoice.
611
	 *
612
	 * @since 1.0.19
613
	 * @param string $value Bill times.
614
	 */
615
	public function set_transaction_id( $value ) {
616
		$this->set_prop( 'transaction_id', $value );
617
	}
618
619
	/**
620
	 * Set date when this subscription started.
621
	 *
622
	 * @since 1.0.19
623
	 * @param string $value strtotime compliant date.
624
	 */
625
	public function set_created( $value ) {
626
        $date = strtotime( $value );
627
628
        if ( $date && $value !== '0000-00-00 00:00:00' ) {
629
            $this->set_prop( 'created', date( 'Y-m-d H:i:s', $date ) );
630
            return;
631
        }
632
633
		$this->set_prop( 'created', '' );
634
635
	}
636
637
	/**
638
	 * Alias for self::set_created().
639
	 *
640
	 * @since 1.0.19
641
	 * @param string $value strtotime compliant date.
642
	 */
643
	public function set_date_created( $value ) {
644
		$this->set_created( $value );
645
    }
646
647
	/**
648
	 * Set the date that the subscription will renew.
649
	 *
650
	 * @since 1.0.19
651
	 * @param string $value strtotime compliant date.
652
	 */
653
	public function set_next_renewal_date( $value ) {
654
		$date = strtotime( $value );
655
656
        if ( $date && $value !== '0000-00-00 00:00:00' ) {
657
            $this->set_prop( 'expiration', date( 'Y-m-d H:i:s', $date ) );
658
            return;
659
		}
660
661
		$this->set_prop( 'expiration', '' );
662
663
	}
664
665
	/**
666
	 * Alias for self::set_next_renewal_date().
667
	 *
668
	 * @since 1.0.19
669
	 * @param string $value strtotime compliant date.
670
	 */
671
	public function set_expiration( $value ) {
672
		$this->set_next_renewal_date( $value );
673
    }
674
675
	/**
676
	 * Set the subscription's trial period.
677
	 *
678
	 * @since 1.0.19
679
	 * @param string $value trial period e.g 1 year.
680
	 */
681
	public function set_trial_period( $value ) {
682
		$this->set_prop( 'trial_period', $value );
683
	}
684
685
	/**
686
	 * Set the subscription's status.
687
	 *
688
	 * @since 1.0.19
689
	 * @param string $new_status    New subscription status.
690
	 */
691
	public function set_status( $new_status ) {
692
693
		// Abort if this is not a valid status;
694
		if ( ! array_key_exists( $new_status, getpaid_get_subscription_statuses() ) ) {
695
			return;
696
		}
697
698
		$old_status = $this->get_status();
699
		$this->set_prop( 'status', $new_status );
700
701
		if ( true === $this->object_read && $old_status !== $new_status ) {
702
			$this->status_transition = array(
0 ignored issues
show
Documentation Bug introduced by
It seems like array('from' => ! empty(...s, 'to' => $new_status) of type array<string,mixed|string> is incompatible with the declared type boolean of property $status_transition.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
703
				'from'   => ! empty( $this->status_transition['from'] ) ? $this->status_transition['from'] : $old_status,
704
				'to'     => $new_status,
705
			);
706
		}
707
708
	}
709
710
	/**
711
	 * Set the subscription's (remote) profile id.
712
	 *
713
	 * @since 1.0.19
714
	 * @param  string $value the remote profile id.
715
	 */
716
	public function set_profile_id( $value ) {
717
		$this->set_prop( 'profile_id', $value );
718
	}
719
720
	/*
721
	|--------------------------------------------------------------------------
722
	| Boolean methods
723
	|--------------------------------------------------------------------------
724
	|
725
	| Return true or false.
726
	|
727
	*/
728
729
	/**
730
     * Checks if the subscription has a given status.
731
	 *
732
	 * @param string|array String or array of strings to check for.
733
	 * @return bool
734
     */
735
    public function has_status( $status ) {
736
        return in_array( $this->get_status(), wpinv_parse_list( $status ) );
737
	}
738
739
	/**
740
     * Checks if the subscription has a trial period.
741
	 *
742
	 * @return bool
743
     */
744
    public function has_trial_period() {
745
		$period = $this->get_trial_period();
746
        return ! empty( $period );
747
	}
748
749
	/**
750
	 * Is the subscription active?
751
	 *
752
	 * @return bool
753
	 */
754
	public function is_active() {
755
		return $this->has_status( 'active trialling' ) && $this->get_expiration_time() > current_time( 'mysql' );
756
	}
757
758
	/**
759
	 * Is the subscription expired?
760
	 *
761
	 * @return bool
762
	 */
763
	public function is_expired() {
764
		return $this->has_status( 'expired' ) || ( $this->has_status( 'active cancelled trialling' ) && $this->get_expiration_time() < current_time( 'mysql' ) );
765
	}
766
767
	/*
768
	|--------------------------------------------------------------------------
769
	| Additional methods
770
	|--------------------------------------------------------------------------
771
	|
772
	| Calculating subscription details.
773
	|
774
	*/
775
776
	/**
777
	 * Backwards compatibilty.
778
	 */
779
	public function create( $data = array() ) {
780
781
		// Set the properties.
782
		if ( is_array( $data ) ) {
783
			$this->set_props( $data );
784
		}
785
786
		// Save the item.
787
		return $this->save();
788
789
	}
790
791
	/**
792
	 * Backwards compatibilty.
793
	 */
794
	public function update( $args = array() ) {
795
		return $this->create( $args );
796
	}
797
798
    /**
799
     * Retrieve renewal payments for a subscription
800
     *
801
     * @since  1.0.0
802
     * @return WP_Post[]
803
     */
804
    public function get_child_payments() {
805
        return get_posts(
0 ignored issues
show
Bug Best Practice introduced by
The expression return get_posts(array('...ype' => 'wpi_invoice')) returns an array which contains values of type integer which are incompatible with the documented value type WP_Post.
Loading history...
806
			array(
807
            	'post_parent'    => $this->get_parent_payment_id(),
808
            	'numberposts'    => -1,
809
            	'post_status'    => array( 'publish', 'wpi-processing', 'wpi-renewal' ),
810
            	'orderby'        => 'ID',
811
            	'order'          => 'DESC',
812
            	'post_type'      => 'wpi_invoice'
813
			)
814
		);
815
    }
816
817
    /**
818
     * Counts the number of invoices generated for the subscription.
819
     *
820
     * @since  1.0.0
821
     * @return int
822
     */
823
    public function get_total_payments() {
824
		global $wpdb;
825
826
		$count = $wpdb->get_var(
827
			$wpdb->prepare(
828
				"SELECT COUNT(ID) FROM $wpdb->posts WHERE post_parent=%d AND post_status IN ( 'publish', 'wpi-processing', 'wpi-renewal' )",
829
				$this->get_parent_invoice_id()
830
			)
831
		);
832
833
		// Maybe include parent invoice.
834
        if ( ! $this->has_status( 'pending' ) ) {
835
            $count++;
836
        }
837
838
        return $count;
839
    }
840
841
    /**
842
     * Counts the number of payments for the subscription.
843
     *
844
     * @since  1.0.2
845
     * @return int
846
     */
847
    public function get_times_billed() {
848
        $times_billed = $this->get_total_payments();
849
850
        if ( $this->has_trial_period() && $times_billed > 0 ) {
851
            $times_billed--;
852
        }
853
854
        return $times_billed;
855
    }
856
857
    /**
858
     * Records a new payment on the subscription
859
     *
860
     * @since  2.4
861
     * @param  array $args Array of values for the payment, including amount and transaction ID
862
	 * @param  WPInv_Invoice $invoice If adding an existing invoice.
863
     * @return bool
864
     */
865
    public function add_payment( $args = array(), $invoice = false ) {
866
867
		// Process each payment once.
868
        if ( ! empty( $args['transaction_id'] ) && $this->payment_exists( $args['transaction_id'] ) ) {
869
            return false;
870
        }
871
872
		// Are we creating a new invoice?
873
		if ( empty( $invoice ) ) {
874
			$invoice = $this->create_payment();
875
876
			if ( empty( $invoice ) ) {
877
				return false;
878
			}
879
880
			$invoice->set_status( 'wpi-renewal' );
881
882
		}
883
884
		// Maybe set a transaction id.
885
		if ( ! empty( $args['transaction_id'] ) ) {
886
			$invoice->set_transaction_id( $args['transaction_id'] );
887
		}
888
889
		// Set the completed date.
890
		$invoice->set_completed_date( current_time( 'mysql' ) );
891
892
		// And the gateway.
893
		if ( ! empty( $args['gateway'] ) ) {
894
			$invoice->set_gateway( $args['gateway'] );
895
		}
896
897
		$invoice->save();
898
899
		if ( ! $invoice->get_id() ) {
900
			return 0;
0 ignored issues
show
Bug Best Practice introduced by
The expression return 0 returns the type integer which is incompatible with the documented return type boolean.
Loading history...
901
		}
902
903
		do_action( 'getpaid_after_create_subscription_renewal_invoice', $invoice, $this );
904
		do_action( 'wpinv_recurring_add_subscription_payment', $invoice, $this );
905
        do_action( 'wpinv_recurring_record_payment', $invoice->get_id(), $this->get_parent_invoice_id(), $invoice->get_recurring_total(), $invoice->get_transaction_id() );
906
907
        update_post_meta( $invoice->get_id(), '_wpinv_subscription_id', $this->id );
908
909
        return $invoice->get_id();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $invoice->get_id() returns the type integer which is incompatible with the documented return type boolean.
Loading history...
910
	}
911
912
	/**
913
     * Creates a new invoice and returns it.
914
     *
915
     * @since  1.0.19
916
     * @return WPInv_Invoice|bool
917
     */
918
    public function create_payment() {
919
920
		$parent_invoice = $this->get_parent_payment();
921
922
		if ( ! $parent_invoice->get_id() ) {
923
			return false;
924
		}
925
926
		// Duplicate the parent invoice.
927
		$invoice = new WPInv_Invoice();
928
		$invoice->set_props( $parent_invoice->get_data() );
929
		$invoice->set_id( 0 );
930
		$invoice->set_parent_id( $parent_invoice->get_id() );
931
		$invoice->set_transaction_id( '' );
932
		$invoice->set_key( $invoice->generate_key( 'renewal_' ) );
933
		$invoice->set_number( '' );
934
		$invoice->set_completed_date( '' );
935
		$invoice->set_status( 'wpi-pending' );
936
		$invoice->recalculate_total();
937
		$invoice->save();
938
939
		return $invoice->get_id() ? $invoice : false;
940
    }
941
942
	/**
943
	 * Renews or completes a subscription
944
	 *
945
	 * @since  1.0.0
946
	 * @return int The subscription's id
947
	 */
948
	public function renew() {
949
950
		// Complete subscription if applicable
951
		if ( $this->get_bill_times() > 0 && $this->get_times_billed() >= $this->get_bill_times() ) {
952
			return $this->complete();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->complete() could also return false which is incompatible with the documented return type integer. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
953
		}
954
955
		// Calculate new expiration
956
		$frequency      = $this->get_frequency();
957
		$period         = $this->get_period();
958
		$new_expiration = strtotime( "+ $frequency $period", $this->get_expiration_time() );
959
960
		$this->set_expiration( date( 'Y-m-d H:i:s',$new_expiration ) );
961
		$this->set_status( 'active' );
962
		return $this->save();
963
964
		do_action( 'getpaid_subscription_renewed', $this );
0 ignored issues
show
Unused Code introduced by
do_action('getpaid_subscription_renewed', $this) is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
965
966
	}
967
968
	/**
969
	 * Marks a subscription as completed
970
	 *
971
	 * Subscription is completed when the number of payments matches the billing_times field
972
	 *
973
	 * @since  1.0.0
974
	 * @return int|bool Subscription id or false if the subscription is cancelled.
975
	 */
976
	public function complete() {
977
978
		// Only mark a subscription as complete if it's not already cancelled.
979
		if ( $this->has_status( 'cancelled' ) ) {
980
			return false;
981
		}
982
983
		$this->set_status( 'completed' );
984
		return $this->save();
985
986
	}
987
988
	/**
989
	 * Marks a subscription as expired
990
	 *
991
	 * @since  1.0.0
992
	 * @param  bool $check_expiration
993
	 * @return int|bool Subscription id or false if $check_expiration is true and expiration date is in the future.
994
	 */
995
	public function expire( $check_expiration = false ) {
996
997
		if ( $check_expiration && $this->get_expiration_time() > current_time( 'timestamp' ) ) {
998
			// Do not mark as expired since real expiration date is in the future
999
			return false;
1000
		}
1001
1002
		$this->set_status( 'expired' );
1003
		return $this->save();
1004
1005
	}
1006
1007
	/**
1008
	 * Marks a subscription as failing
1009
	 *
1010
	 * @since  2.4.2
1011
	 * @return int Subscription id.
1012
	 */
1013
	public function failing() {
1014
		$this->set_status( 'failing' );
1015
		return $this->save();
1016
	}
1017
1018
    /**
1019
     * Marks a subscription as cancelled
1020
     *
1021
     * @since  1.0.0
1022
     * @return int Subscription id.
1023
     */
1024
    public function cancel() {
1025
		$this->set_status( 'cancelled' );
1026
		return $this->save();
1027
    }
1028
1029
	/**
1030
	 * Determines if a subscription can be cancelled both locally and with a payment processor.
1031
	 *
1032
	 * @since  1.0.0
1033
	 * @return bool
1034
	 */
1035
	public function can_cancel() {
1036
		return apply_filters( 'wpinv_subscription_can_cancel', $this->has_status( $this->get_cancellable_statuses() ), $this );
1037
	}
1038
1039
    /**
1040
     * Returns an array of subscription statuses that can be cancelled
1041
     *
1042
     * @access      public
1043
     * @since       1.0.0
1044
     * @return      array
1045
     */
1046
    public function get_cancellable_statuses() {
1047
        return apply_filters( 'wpinv_recurring_cancellable_statuses', array( 'active', 'trialling', 'failing' ) );
1048
    }
1049
1050
	/**
1051
	 * Retrieves the URL to cancel subscription
1052
	 *
1053
	 * @since  1.0.0
1054
	 * @return string
1055
	 */
1056
	public function get_cancel_url() {
1057
		$url = wp_nonce_url( add_query_arg( array( 'wpinv_action' => 'cancel_subscription', 'sub_id' => $this->get_id() ) ), 'wpinv-recurring-cancel' );
1058
		return apply_filters( 'wpinv_subscription_cancel_url', $url, $this );
1059
	}
1060
1061
	/**
1062
	 * Determines if subscription can be manually renewed
1063
	 *
1064
	 * This method is filtered by payment gateways in order to return true on subscriptions
1065
	 * that can be renewed manually
1066
	 *
1067
	 * @since  2.5
1068
	 * @return bool
1069
	 */
1070
	public function can_renew() {
1071
		return apply_filters( 'wpinv_subscription_can_renew', true, $this );
1072
	}
1073
1074
	/**
1075
	 * Retrieves the URL to renew a subscription
1076
	 *
1077
	 * @since  2.5
1078
	 * @return string
1079
	 */
1080
	public function get_renew_url() {
1081
		$url = wp_nonce_url( add_query_arg( array( 'wpinv_action' => 'renew_subscription', 'sub_id' => $this->get_id ) ), 'wpinv-recurring-renew' );
0 ignored issues
show
Bug Best Practice introduced by
The property get_id does not exist on WPInv_Subscription. Since you implemented __get, consider adding a @property annotation.
Loading history...
1082
		return apply_filters( 'wpinv_subscription_renew_url', $url, $this );
1083
	}
1084
1085
	/**
1086
	 * Determines if subscription can have their payment method updated
1087
	 *
1088
	 * @since  1.0.0
1089
	 * @return bool
1090
	 */
1091
	public function can_update() {
1092
		return apply_filters( 'wpinv_subscription_can_update', false, $this );
1093
	}
1094
1095
	/**
1096
	 * Retrieves the URL to update subscription
1097
	 *
1098
	 * @since  1.0.0
1099
	 * @return string
1100
	 */
1101
	public function get_update_url() {
1102
		$url = add_query_arg( array( 'action' => 'update', 'subscription_id' => $this->get_id() ) );
1103
		return apply_filters( 'wpinv_subscription_update_url', $url, $this );
1104
	}
1105
1106
	/**
1107
	 * Retrieves the subscription status label
1108
	 *
1109
	 * @since  1.0.0
1110
	 * @return string
1111
	 */
1112
	public function get_status_label() {
1113
		return getpaid_get_subscription_status_label( $this->get_status() );
1114
	}
1115
1116
	/**
1117
	 * Retrieves the subscription status class
1118
	 *
1119
	 * @since  1.0.19
1120
	 * @return string
1121
	 */
1122
	public function get_status_class() {
1123
		$statuses = getpaid_get_subscription_status_classes();
1124
		return isset( $statuses[ $this->get_status() ] ) ? $statuses[ $this->get_status() ] : 'text-white bg-secondary';
1125
	}
1126
1127
    /**
1128
     * Retrieves the subscription status label
1129
     *
1130
     * @since  1.0.0
1131
     * @return string
1132
     */
1133
    public function get_status_label_html() {
1134
1135
		$status_label = sanitize_text_field( $this->get_status_label() );
1136
		$class        = sanitize_html_class( $this->get_status_class() );
1137
		$status       = sanitize_html_class( $this->get_status_label() );
1138
1139
		"<span class='bsui'><small class='d-inline-block py-1 px-2 rounded $class $status'>$status_label</small></span>";
1140
    }
1141
1142
    /**
1143
     * Determines if a payment exists with the specified transaction ID
1144
     *
1145
     * @since  2.4
1146
     * @param  string $txn_id The transaction ID from the merchant processor
1147
     * @return bool
1148
     */
1149
    public function payment_exists( $txn_id = '' ) {
1150
		$invoice_id = WPInv_Invoice::get_invoice_id_by_field( $txn_id, 'transaction_id' );
1151
        return ! empty( $invoice_id );
1152
	}
1153
1154
	/**
1155
	 * Handle the status transition.
1156
	 */
1157
	protected function status_transition() {
1158
		$status_transition = $this->status_transition;
1159
1160
		// Reset status transition variable.
1161
		$this->status_transition = false;
1162
1163
		if ( $status_transition ) {
1164
			try {
1165
1166
				// Fire a hook for the status change.
1167
				do_action( 'wpinv_subscription_' . $status_transition['to'], $this->get_id(), $this, $status_transition );
1168
				do_action( 'getpaid_subscription_' . $status_transition['to'], $this, $status_transition );
1169
1170
				if ( ! empty( $status_transition['from'] ) ) {
1171
1172
					/* translators: 1: old subscription status 2: new subscription status */
1173
					$transition_note = sprintf( __( 'Subscription status changed from %1$s to %2$s.', 'invoicing' ), getpaid_get_subscription_status_label( $status_transition['from'] ), getpaid_get_subscription_status_label( $status_transition['to'] ) );
1174
1175
					// Fire another hook.
1176
					do_action( 'getpaid_subscription_status_' . $status_transition['from'] . '_to_' . $status_transition['to'], $this->get_id(), $this );
1177
					do_action( 'getpaid_subscription_status_changed', $this->get_id(), $status_transition['from'], $status_transition['to'], $this );
1178
1179
					// Note the transition occurred.
1180
					$this->get_parent_payment()->add_note( $transition_note, false, false, true );
1181
1182
				} else {
1183
					/* translators: %s: new invoice status */
1184
					$transition_note = sprintf( __( 'Subscription status set to %s.', 'invoicing' ), getpaid_get_subscription_status_label( $status_transition['to'] ) );
1185
1186
					// Note the transition occurred.
1187
					$this->get_parent_payment()->add_note( $transition_note, false, false, true );
1188
1189
				}
1190
			} catch ( Exception $e ) {
1191
				$this->get_parent_payment()->add_note( __( 'Error during subscription status transition.', 'invoicing' ) . ' ' . $e->getMessage() );
1192
			}
1193
		}
1194
1195
	}
1196
1197
	/**
1198
	 * Save data to the database.
1199
	 *
1200
	 * @since 1.0.19
1201
	 * @return int subscription ID
1202
	 */
1203
	public function save() {
1204
		parent::save();
1205
		$this->status_transition();
1206
		return $this->get_id();
1207
	}
1208
1209
}
1210