Passed
Push — master ( 6621fa...568391 )
by Brian
06:12
created

WPInv_Subscription   F

Complexity

Total Complexity 137

Size/Duplication

Total Lines 1223
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 137
eloc 265
c 5
b 0
f 0
dl 0
loc 1223
rs 2

81 Methods

Rating   Name   Duplication   Size   Complexity  
A set_initial_amount() 0 2 1
A get_cancellable_statuses() 0 2 1
A get_trial_period() 0 2 1
A get_child_payments() 0 9 1
A can_cancel() 0 2 1
A get_customer() 0 2 1
A set_date_created() 0 2 1
A get_product_id() 0 2 1
A expire() 0 9 3
A is_last_renewal() 0 3 2
A create() 0 9 2
A set_original_payment_id() 0 2 1
A get_times_billed() 0 8 3
A set_transaction_id() 0 2 1
A has_trial_period() 0 3 1
A set_parent_payment_id() 0 2 1
B __construct() 0 21 9
A get_customer_id() 0 2 1
A clear_cache() 0 5 1
A set_frequency() 0 3 2
A set_bill_times() 0 2 1
A get_original_payment_id() 0 2 1
B add_payment() 0 45 8
A complete() 0 9 2
A get_parent_payment() 0 2 1
A get_frequency() 0 2 1
A set_customer_id() 0 2 1
A get_time_created() 0 3 2
A set_parent_invoice_id() 0 2 1
A get_profile_id() 0 2 1
A failing() 0 3 1
A get_created() 0 2 1
A cancel() 0 3 1
A get_next_renewal_date() 0 2 1
A set_expiration() 0 2 1
A get_gateway() 0 2 1
A set_trial_period() 0 2 1
A get_next_renewal_date_gmt() 0 7 2
A get_period() 0 2 1
A get_parent_invoice() 0 2 1
A get_cancel_url() 0 3 1
A set_period() 0 2 1
A get_recurring_amount() 0 2 1
A get_expiration_time() 0 9 4
A _isset() 0 2 2
A is_active() 0 2 2
A update() 0 2 1
A is_expired() 0 2 3
A set_profile_id() 0 2 1
A get_date_created() 0 2 1
A get_parent_invoice_id() 0 2 1
A get_expiration() 0 2 1
A get_status() 0 2 1
A has_status() 0 2 1
A get_date_created_gmt() 0 7 2
A set_created() 0 9 3
A get_transaction_id() 0 2 1
A get_initial_amount() 0 2 1
A get_parent_payment_id() 0 2 1
A get_total_payments() 0 16 2
A set_product_id() 0 2 1
A get_bill_times() 0 2 1
A set_next_renewal_date() 0 9 3
B get_subscription_id_by_field() 0 46 7
A set_recurring_amount() 0 2 1
A set_status() 0 14 5
A create_payment() 0 23 3
A get_product() 0 2 1
A save() 0 4 1
A get_update_url() 0 3 1
A get_renew_url() 0 3 1
A get_status_class() 0 3 2
A can_renew() 0 2 1
A status_transition() 0 35 4
A get_view_url() 0 3 1
A can_update() 0 2 1
A get_status_label_html() 0 7 1
A payment_exists() 0 3 1
A get_status_label() 0 2 1
A activate() 0 4 2
A renew() 0 17 2

How to fix   Complexity   

Complex Class

Complex classes like WPInv_Subscription often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WPInv_Subscription, and based on these observations, apply Extract Interface, too.

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'      => '',
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
	/**
66
	 * Get the subscription if ID is passed, otherwise the subscription is new and empty.
67
	 *
68
	 * @param  int|string|object|WPInv_Subscription $subscription Subscription id, profile_id, or object to read.
69
	 * @param  bool $deprecated
70
	 */
71
	function __construct( $subscription = 0, $deprecated = false ) {
72
73
		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

73
		parent::__construct( /** @scrutinizer ignore-type */ $subscription );
Loading history...
74
75
		if ( ! $deprecated && ! empty( $subscription ) && is_numeric( $subscription ) ) {
76
			$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

76
			$this->set_id( /** @scrutinizer ignore-type */ $subscription );
Loading history...
77
		} elseif ( $subscription instanceof self ) {
78
			$this->set_id( $subscription->get_id() );
79
		} elseif ( ! empty( $subscription->id ) ) {
80
			$this->set_id( $subscription->id );
81
		} elseif ( $deprecated && $subscription_id = self::get_subscription_id_by_field( $subscription, 'profile_id' ) ) {
82
			$this->set_id( $subscription_id );
83
		} else {
84
			$this->set_object_read( true );
85
		}
86
87
		// Load the datastore.
88
		$this->data_store = GetPaid_Data_Store::load( $this->data_store_name );
89
90
		if ( $this->get_id() > 0 ) {
91
			$this->data_store->read( $this );
92
		}
93
94
	}
95
96
	/**
97
	 * Given an invoice id, profile id, transaction id, it returns the subscription's id.
98
	 *
99
	 *
100
	 * @static
101
	 * @param string $value
102
	 * @param string $field Either invoice_id, transaction_id or profile_id.
103
	 * @since 1.0.19
104
	 * @return int
105
	 */
106
	public static function get_subscription_id_by_field( $value, $field = 'profile_id' ) {
107
        global $wpdb;
108
109
		// Trim the value.
110
		$value = trim( $value );
111
112
		if ( empty( $value ) ) {
113
			return 0;
114
		}
115
116
		if ( 'invoice_id' == $field ) {
117
			$field = 'parent_payment_id';
118
		}
119
120
        // Valid fields.
121
        $fields = array(
122
			'parent_payment_id',
123
			'transaction_id',
124
			'profile_id'
125
		);
126
127
		// Ensure a field has been passed.
128
		if ( empty( $field ) || ! in_array( $field, $fields ) ) {
129
			return 0;
130
		}
131
132
		// Maybe retrieve from the cache.
133
		$subscription_id   = wp_cache_get( $value, "getpaid_subscription_{$field}s_to_subscription_ids" );
134
		if ( ! empty( $subscription_id ) ) {
135
			return $subscription_id;
136
		}
137
138
        // Fetch from the db.
139
        $table            = $wpdb->prefix . 'wpinv_subscriptions';
140
        $subscription_id  = (int) $wpdb->get_var(
141
            $wpdb->prepare( "SELECT `id` FROM $table WHERE `$field`=%s LIMIT 1", $value )
142
        );
143
144
		if ( empty( $subscription_id ) ) {
145
			return 0;
146
		}
147
148
		// Update the cache with our data.
149
		wp_cache_set( $value, $subscription_id, "getpaid_subscription_{$field}s_to_subscription_ids" );
150
151
		return $subscription_id;
152
	}
153
154
	/**
155
     * Clears the subscription's cache.
156
     */
157
    public function clear_cache() {
158
		wp_cache_delete( $this->get_parent_payment_id(), 'getpaid_subscription_parent_payment_ids_to_subscription_ids' );
159
		wp_cache_delete( $this->get_transaction_id(), 'getpaid_subscription_transaction_ids_to_subscription_ids' );
160
		wp_cache_delete( $this->get_profile_id(), 'getpaid_subscription_profile_ids_to_subscription_ids' );
161
		wp_cache_delete( $this->get_id(), 'getpaid_subscriptions' );
162
	}
163
164
	/**
165
     * Checks if a subscription key is set.
166
     */
167
    public function _isset( $key ) {
168
        return isset( $this->data[$key] ) || method_exists( $this, "get_$key" );
169
	}
170
171
	/*
172
	|--------------------------------------------------------------------------
173
	| CRUD methods
174
	|--------------------------------------------------------------------------
175
	|
176
	| Methods which create, read, update and delete subscriptions from the database.
177
	|
178
    */
179
180
	/*
181
	|--------------------------------------------------------------------------
182
	| Getters
183
	|--------------------------------------------------------------------------
184
	*/
185
186
	/**
187
	 * Get customer id.
188
	 *
189
	 * @since 1.0.19
190
	 * @param  string $context View or edit context.
191
	 * @return int
192
	 */
193
	public function get_customer_id( $context = 'view' ) {
194
		return (int) $this->get_prop( 'customer_id', $context );
195
	}
196
197
	/**
198
	 * Get customer information.
199
	 *
200
	 * @since 1.0.19
201
	 * @param  string $context View or edit context.
202
	 * @return WP_User|false WP_User object on success, false on failure.
203
	 */
204
	public function get_customer( $context = 'view' ) {
205
		return get_userdata( $this->get_customer_id( $context ) );
206
	}
207
208
	/**
209
	 * Get parent invoice id.
210
	 *
211
	 * @since 1.0.19
212
	 * @param  string $context View or edit context.
213
	 * @return int
214
	 */
215
	public function get_parent_invoice_id( $context = 'view' ) {
216
		return (int) $this->get_prop( 'parent_payment_id', $context );
217
	}
218
219
	/**
220
	 * Alias for self::get_parent_invoice_id().
221
	 *
222
	 * @since 1.0.19
223
	 * @param  string $context View or edit context.
224
	 * @return int
225
	 */
226
    public function get_parent_payment_id( $context = 'view' ) {
227
        return $this->get_parent_invoice_id( $context );
228
	}
229
230
	/**
231
     * Alias for self::get_parent_invoice_id().
232
     *
233
     * @since  1.0.0
234
     * @return int
235
     */
236
    public function get_original_payment_id( $context = 'view' ) {
237
        return $this->get_parent_invoice_id( $context );
238
    }
239
240
	/**
241
	 * Get parent invoice.
242
	 *
243
	 * @since 1.0.19
244
	 * @param  string $context View or edit context.
245
	 * @return WPInv_Invoice
246
	 */
247
	public function get_parent_invoice( $context = 'view' ) {
248
		return new WPInv_Invoice( $this->get_parent_invoice_id( $context ) );
249
	}
250
251
	/**
252
	 * Alias for self::get_parent_invoice().
253
	 *
254
	 * @since 1.0.19
255
	 * @param  string $context View or edit context.
256
	 * @return WPInv_Invoice
257
	 */
258
    public function get_parent_payment( $context = 'view' ) {
259
        return $this->get_parent_invoice( $context );
260
	}
261
262
	/**
263
	 * Get subscription's product id.
264
	 *
265
	 * @since 1.0.19
266
	 * @param  string $context View or edit context.
267
	 * @return int
268
	 */
269
	public function get_product_id( $context = 'view' ) {
270
		return (int) $this->get_prop( 'product_id', $context );
271
	}
272
273
	/**
274
	 * Get the subscription product.
275
	 *
276
	 * @since 1.0.19
277
	 * @param  string $context View or edit context.
278
	 * @return WPInv_Item
279
	 */
280
	public function get_product( $context = 'view' ) {
281
		return new WPInv_Item( $this->get_product_id( $context ) );
282
	}
283
284
	/**
285
	 * Get parent invoice's gateway.
286
	 *
287
	 * Here for backwards compatibility.
288
	 *
289
	 * @since 1.0.19
290
	 * @param  string $context View or edit context.
291
	 * @return string
292
	 */
293
	public function get_gateway( $context = 'view' ) {
294
		return $this->get_parent_invoice( $context )->get_gateway();
295
	}
296
297
	/**
298
	 * Get the period of a renewal.
299
	 *
300
	 * @since 1.0.19
301
	 * @param  string $context View or edit context.
302
	 * @return string
303
	 */
304
	public function get_period( $context = 'view' ) {
305
		return $this->get_prop( 'period', $context );
306
	}
307
308
	/**
309
	 * Get number of periods each renewal is valid for.
310
	 *
311
	 * @since 1.0.19
312
	 * @param  string $context View or edit context.
313
	 * @return int
314
	 */
315
	public function get_frequency( $context = 'view' ) {
316
		return (int) $this->get_prop( 'frequency', $context );
317
	}
318
319
	/**
320
	 * Get the initial amount for the subscription.
321
	 *
322
	 * @since 1.0.19
323
	 * @param  string $context View or edit context.
324
	 * @return float
325
	 */
326
	public function get_initial_amount( $context = 'view' ) {
327
		return (float) wpinv_sanitize_amount( $this->get_prop( 'initial_amount', $context ) );
328
	}
329
330
	/**
331
	 * Get the recurring amount for the subscription.
332
	 *
333
	 * @since 1.0.19
334
	 * @param  string $context View or edit context.
335
	 * @return float
336
	 */
337
	public function get_recurring_amount( $context = 'view' ) {
338
		return (float) wpinv_sanitize_amount( $this->get_prop( 'recurring_amount', $context ) );
339
	}
340
341
	/**
342
	 * Get number of times that this subscription can be renewed.
343
	 *
344
	 * @since 1.0.19
345
	 * @param  string $context View or edit context.
346
	 * @return int
347
	 */
348
	public function get_bill_times( $context = 'view' ) {
349
		return (int) $this->get_prop( 'bill_times', $context );
350
	}
351
352
	/**
353
	 * Get transaction id of this subscription's parent invoice.
354
	 *
355
	 * @since 1.0.19
356
	 * @param  string $context View or edit context.
357
	 * @return string
358
	 */
359
	public function get_transaction_id( $context = 'view' ) {
360
		return $this->get_prop( 'transaction_id', $context );
361
	}
362
363
	/**
364
	 * Get the date that the subscription was created.
365
	 *
366
	 * @since 1.0.19
367
	 * @param  string $context View or edit context.
368
	 * @return string
369
	 */
370
	public function get_created( $context = 'view' ) {
371
		return $this->get_prop( 'created', $context );
372
	}
373
374
	/**
375
	 * Alias for self::get_created().
376
	 *
377
	 * @since 1.0.19
378
	 * @param  string $context View or edit context.
379
	 * @return string
380
	 */
381
	public function get_date_created( $context = 'view' ) {
382
		return $this->get_created( $context );
383
	}
384
385
	/**
386
	 * Retrieves the creation date in a timestamp
387
	 *
388
	 * @since  1.0.0
389
	 * @return int
390
	 */
391
	public function get_time_created() {
392
		$created = $this->get_date_created();
393
		return empty( $created ) ? current_time( 'timestamp' ) : strtotime( $created, current_time( 'timestamp' ) );
394
	}
395
396
	/**
397
	 * Get GMT date when the subscription was created.
398
	 *
399
	 * @since 1.0.19
400
	 * @param  string $context View or edit context.
401
	 * @return string
402
	 */
403
	public function get_date_created_gmt( $context = 'view' ) {
404
        $date = $this->get_date_created( $context );
405
406
        if ( $date ) {
407
            $date = get_gmt_from_date( $date );
408
        }
409
		return $date;
410
	}
411
412
	/**
413
	 * Get the date that the subscription will renew.
414
	 *
415
	 * @since 1.0.19
416
	 * @param  string $context View or edit context.
417
	 * @return string
418
	 */
419
	public function get_next_renewal_date( $context = 'view' ) {
420
		return $this->get_prop( 'expiration', $context );
421
	}
422
423
	/**
424
	 * Alias for self::get_next_renewal_date().
425
	 *
426
	 * @since 1.0.19
427
	 * @param  string $context View or edit context.
428
	 * @return string
429
	 */
430
	public function get_expiration( $context = 'view' ) {
431
		return $this->get_next_renewal_date( $context );
432
	}
433
434
	/**
435
	 * Retrieves the expiration date in a timestamp
436
	 *
437
	 * @since  1.0.0
438
	 * @return int
439
	 */
440
	public function get_expiration_time() {
441
		$expiration = $this->get_expiration();
442
443
		if ( empty( $expiration ) || '0000-00-00 00:00:00' == $expiration ) {
444
			return current_time( 'timestamp' );
445
		}
446
447
		$expiration = strtotime( $expiration, current_time( 'timestamp' ) );
448
		return $expiration < current_time( 'timestamp' ) ? current_time( 'timestamp' ) : $expiration;
449
	}
450
451
	/**
452
	 * Get GMT date when the subscription will renew.
453
	 *
454
	 * @since 1.0.19
455
	 * @param  string $context View or edit context.
456
	 * @return string
457
	 */
458
	public function get_next_renewal_date_gmt( $context = 'view' ) {
459
        $date = $this->get_next_renewal_date( $context );
460
461
        if ( $date ) {
462
            $date = get_gmt_from_date( $date );
463
        }
464
		return $date;
465
	}
466
467
	/**
468
	 * Get the subscription's trial period.
469
	 *
470
	 * @since 1.0.19
471
	 * @param  string $context View or edit context.
472
	 * @return string
473
	 */
474
	public function get_trial_period( $context = 'view' ) {
475
		return $this->get_prop( 'trial_period', $context );
476
	}
477
478
	/**
479
	 * Get the subscription's status.
480
	 *
481
	 * @since 1.0.19
482
	 * @param  string $context View or edit context.
483
	 * @return string
484
	 */
485
	public function get_status( $context = 'view' ) {
486
		return $this->get_prop( 'status', $context );
487
	}
488
489
	/**
490
	 * Get the subscription's profile id.
491
	 *
492
	 * @since 1.0.19
493
	 * @param  string $context View or edit context.
494
	 * @return string
495
	 */
496
	public function get_profile_id( $context = 'view' ) {
497
		return $this->get_prop( 'profile_id', $context );
498
	}
499
500
	/*
501
	|--------------------------------------------------------------------------
502
	| Setters
503
	|--------------------------------------------------------------------------
504
	*/
505
506
	/**
507
	 * Set customer id.
508
	 *
509
	 * @since 1.0.19
510
	 * @param  int $value The customer's id.
511
	 */
512
	public function set_customer_id( $value ) {
513
		$this->set_prop( 'customer_id', (int) $value );
514
	}
515
516
	/**
517
	 * Set parent invoice id.
518
	 *
519
	 * @since 1.0.19
520
	 * @param  int $value The parent invoice id.
521
	 */
522
	public function set_parent_invoice_id( $value ) {
523
		$this->set_prop( 'parent_payment_id', (int) $value );
524
	}
525
526
	/**
527
	 * Alias for self::set_parent_invoice_id().
528
	 *
529
	 * @since 1.0.19
530
	 * @param  int $value The parent invoice id.
531
	 */
532
    public function set_parent_payment_id( $value ) {
533
        $this->set_parent_invoice_id( $value );
534
	}
535
536
	/**
537
     * Alias for self::set_parent_invoice_id().
538
     *
539
     * @since 1.0.19
540
	 * @param  int $value The parent invoice id.
541
     */
542
    public function set_original_payment_id( $value ) {
543
        $this->set_parent_invoice_id( $value );
544
	}
545
546
	/**
547
	 * Set subscription's product id.
548
	 *
549
	 * @since 1.0.19
550
	 * @param  int $value The subscription product id.
551
	 */
552
	public function set_product_id( $value ) {
553
		$this->set_prop( 'product_id', (int) $value );
554
	}
555
556
	/**
557
	 * Set the period of a renewal.
558
	 *
559
	 * @since 1.0.19
560
	 * @param  string $value The renewal period.
561
	 */
562
	public function set_period( $value ) {
563
		$this->set_prop( 'period', $value );
564
	}
565
566
	/**
567
	 * Set number of periods each renewal is valid for.
568
	 *
569
	 * @since 1.0.19
570
	 * @param  int $value The subscription frequency.
571
	 */
572
	public function set_frequency( $value ) {
573
		$value = empty( $value ) ? 1 : (int) $value;
574
		$this->set_prop( 'frequency', absint( $value ) );
575
	}
576
577
	/**
578
	 * Set the initial amount for the subscription.
579
	 *
580
	 * @since 1.0.19
581
	 * @param  float $value The initial subcription amount.
582
	 */
583
	public function set_initial_amount( $value ) {
584
		$this->set_prop( 'initial_amount', wpinv_sanitize_amount( $value ) );
585
	}
586
587
	/**
588
	 * Set the recurring amount for the subscription.
589
	 *
590
	 * @since 1.0.19
591
	 * @param  float $value The recurring subcription amount.
592
	 */
593
	public function set_recurring_amount( $value ) {
594
		$this->set_prop( 'recurring_amount', wpinv_sanitize_amount( $value ) );
595
	}
596
597
	/**
598
	 * Set number of times that this subscription can be renewed.
599
	 *
600
	 * @since 1.0.19
601
	 * @param  int $value Bill times.
602
	 */
603
	public function set_bill_times( $value ) {
604
		$this->set_prop( 'bill_times', (int) $value );
605
	}
606
607
	/**
608
	 * Get transaction id of this subscription's parent invoice.
609
	 *
610
	 * @since 1.0.19
611
	 * @param string $value Bill times.
612
	 */
613
	public function set_transaction_id( $value ) {
614
		$this->set_prop( 'transaction_id', sanitize_text_field( $value ) );
615
	}
616
617
	/**
618
	 * Set date when this subscription started.
619
	 *
620
	 * @since 1.0.19
621
	 * @param string $value strtotime compliant date.
622
	 */
623
	public function set_created( $value ) {
624
        $date = strtotime( $value );
625
626
        if ( $date && $value !== '0000-00-00 00:00:00' ) {
627
            $this->set_prop( 'created', date( 'Y-m-d H:i:s', $date ) );
628
            return;
629
        }
630
631
		$this->set_prop( 'created', '' );
632
633
	}
634
635
	/**
636
	 * Alias for self::set_created().
637
	 *
638
	 * @since 1.0.19
639
	 * @param string $value strtotime compliant date.
640
	 */
641
	public function set_date_created( $value ) {
642
		$this->set_created( $value );
643
    }
644
645
	/**
646
	 * Set the date that the subscription will renew.
647
	 *
648
	 * @since 1.0.19
649
	 * @param string $value strtotime compliant date.
650
	 */
651
	public function set_next_renewal_date( $value ) {
652
		$date = strtotime( $value );
653
654
        if ( $date && $value !== '0000-00-00 00:00:00' ) {
655
            $this->set_prop( 'expiration', date( 'Y-m-d H:i:s', $date ) );
656
            return;
657
		}
658
659
		$this->set_prop( 'expiration', '' );
660
661
	}
662
663
	/**
664
	 * Alias for self::set_next_renewal_date().
665
	 *
666
	 * @since 1.0.19
667
	 * @param string $value strtotime compliant date.
668
	 */
669
	public function set_expiration( $value ) {
670
		$this->set_next_renewal_date( $value );
671
    }
672
673
	/**
674
	 * Set the subscription's trial period.
675
	 *
676
	 * @since 1.0.19
677
	 * @param string $value trial period e.g 1 year.
678
	 */
679
	public function set_trial_period( $value ) {
680
		$this->set_prop( 'trial_period', $value );
681
	}
682
683
	/**
684
	 * Set the subscription's status.
685
	 *
686
	 * @since 1.0.19
687
	 * @param string $new_status    New subscription status.
688
	 */
689
	public function set_status( $new_status ) {
690
691
		// Abort if this is not a valid status;
692
		if ( ! array_key_exists( $new_status, getpaid_get_subscription_statuses() ) ) {
693
			return;
694
		}
695
696
		$old_status = $this->get_status();
697
		$this->set_prop( 'status', $new_status );
698
699
		if ( true === $this->object_read && $old_status !== $new_status ) {
700
			$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...
701
				'from'   => ! empty( $this->status_transition['from'] ) ? $this->status_transition['from'] : $old_status,
702
				'to'     => $new_status,
703
			);
704
		}
705
706
	}
707
708
	/**
709
	 * Set the subscription's (remote) profile id.
710
	 *
711
	 * @since 1.0.19
712
	 * @param  string $value the remote profile id.
713
	 */
714
	public function set_profile_id( $value ) {
715
		$this->set_prop( 'profile_id', sanitize_text_field( $value ) );
716
	}
717
718
	/*
719
	|--------------------------------------------------------------------------
720
	| Boolean methods
721
	|--------------------------------------------------------------------------
722
	|
723
	| Return true or false.
724
	|
725
	*/
726
727
	/**
728
     * Checks if the subscription has a given status.
729
	 *
730
	 * @param string|array String or array of strings to check for.
731
	 * @return bool
732
     */
733
    public function has_status( $status ) {
734
        return in_array( $this->get_status(), wpinv_clean( wpinv_parse_list( $status ) ) );
735
	}
736
737
	/**
738
     * Checks if the subscription has a trial period.
739
	 *
740
	 * @return bool
741
     */
742
    public function has_trial_period() {
743
		$period = $this->get_trial_period();
744
        return ! empty( $period );
745
	}
746
747
	/**
748
	 * Is the subscription active?
749
	 *
750
	 * @return bool
751
	 */
752
	public function is_active() {
753
		return $this->has_status( 'active trialling' ) && ! $this->is_expired();
754
	}
755
756
	/**
757
	 * Is the subscription expired?
758
	 *
759
	 * @return bool
760
	 */
761
	public function is_expired() {
762
		return $this->has_status( 'expired' ) || ( $this->has_status( 'active cancelled trialling' ) && $this->get_expiration_time() < current_time( 'mysql' ) );
763
	}
764
765
	/**
766
	 * Is this the last renewals?
767
	 *
768
	 * @return bool
769
	 */
770
	public function is_last_renewal() {
771
		$max_bills = $this->get_bill_times();
772
		return ! empty( $max_bills ) && $max_bills >= $this->get_times_billed();
773
	}
774
775
	/*
776
	|--------------------------------------------------------------------------
777
	| Additional methods
778
	|--------------------------------------------------------------------------
779
	|
780
	| Calculating subscription details.
781
	|
782
	*/
783
784
	/**
785
	 * Backwards compatibilty.
786
	 */
787
	public function create( $data = array() ) {
788
789
		// Set the properties.
790
		if ( is_array( $data ) ) {
791
			$this->set_props( $data );
792
		}
793
794
		// Save the item.
795
		return $this->save();
796
797
	}
798
799
	/**
800
	 * Backwards compatibilty.
801
	 */
802
	public function update( $args = array() ) {
803
		return $this->create( $args );
804
	}
805
806
    /**
807
     * Retrieve renewal payments for a subscription
808
     *
809
     * @since  1.0.0
810
     * @return WP_Post[]
811
     */
812
    public function get_child_payments() {
813
        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...
814
			array(
815
            	'post_parent'    => $this->get_parent_payment_id(),
816
            	'numberposts'    => -1,
817
            	'post_status'    => array( 'publish', 'wpi-processing', 'wpi-renewal' ),
818
            	'orderby'        => 'ID',
819
            	'order'          => 'DESC',
820
            	'post_type'      => 'wpi_invoice'
821
			)
822
		);
823
    }
824
825
    /**
826
     * Counts the number of invoices generated for the subscription.
827
     *
828
     * @since  1.0.0
829
     * @return int
830
     */
831
    public function get_total_payments() {
832
		global $wpdb;
833
834
		$count = (int) $wpdb->get_var(
835
			$wpdb->prepare(
836
				"SELECT COUNT(ID) FROM $wpdb->posts WHERE post_parent=%d AND post_status IN ( 'publish', 'wpi-processing', 'wpi-renewal' )",
837
				$this->get_parent_invoice_id()
838
			)
839
		);
840
841
		// Maybe include parent invoice.
842
        if ( ! $this->get_parent_payment()->is_paid() ) {
843
            $count++;
844
        }
845
846
        return $count;
847
    }
848
849
    /**
850
     * Counts the number of payments for the subscription.
851
     *
852
     * @since  1.0.2
853
     * @return int
854
     */
855
    public function get_times_billed() {
856
        $times_billed = $this->get_total_payments();
857
858
        if ( $this->has_trial_period() && $times_billed > 0 ) {
859
            $times_billed--;
860
        }
861
862
        return (int) $times_billed;
863
    }
864
865
    /**
866
     * Records a new payment on the subscription
867
     *
868
     * @since  2.4
869
     * @param  array $args Array of values for the payment, including amount and transaction ID
870
	 * @param  WPInv_Invoice $invoice If adding an existing invoice.
871
     * @return bool
872
     */
873
    public function add_payment( $args = array(), $invoice = false ) {
874
875
		// Process each payment once.
876
        if ( ! empty( $args['transaction_id'] ) && $this->payment_exists( $args['transaction_id'] ) ) {
877
            return false;
878
        }
879
880
		// Are we creating a new invoice?
881
		if ( empty( $invoice ) ) {
882
			$invoice = $this->create_payment();
883
884
			if ( empty( $invoice ) ) {
885
				return false;
886
			}
887
888
			$invoice->set_status( 'wpi-renewal' );
889
890
		}
891
892
		// Maybe set a transaction id.
893
		if ( ! empty( $args['transaction_id'] ) ) {
894
			$invoice->set_transaction_id( $args['transaction_id'] );
895
		}
896
897
		// Set the completed date.
898
		$invoice->set_completed_date( current_time( 'mysql' ) );
899
900
		// And the gateway.
901
		if ( ! empty( $args['gateway'] ) ) {
902
			$invoice->set_gateway( $args['gateway'] );
903
		}
904
905
		$invoice->save();
906
907
		if ( ! $invoice->get_id() ) {
908
			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...
909
		}
910
911
		do_action( 'getpaid_after_create_subscription_renewal_invoice', $invoice, $this );
912
		do_action( 'wpinv_recurring_add_subscription_payment', $invoice, $this );
913
        do_action( 'wpinv_recurring_record_payment', $invoice->get_id(), $this->get_parent_invoice_id(), $invoice->get_recurring_total(), $invoice->get_transaction_id() );
914
915
        update_post_meta( $invoice->get_id(), '_wpinv_subscription_id', $this->id );
916
917
        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...
918
	}
919
920
	/**
921
     * Creates a new invoice and returns it.
922
     *
923
     * @since  1.0.19
924
     * @return WPInv_Invoice|bool
925
     */
926
    public function create_payment() {
927
928
		$parent_invoice = $this->get_parent_payment();
929
930
		if ( ! $parent_invoice->get_id() ) {
931
			return false;
932
		}
933
934
		// Duplicate the parent invoice.
935
		$invoice = new WPInv_Invoice();
936
		$invoice->set_props( $parent_invoice->get_data() );
937
		$invoice->set_id( 0 );
938
		$invoice->set_items( $parent_invoice->get_items() );
939
		$invoice->set_parent_id( $parent_invoice->get_id() );
940
		$invoice->set_transaction_id( '' );
941
		$invoice->set_key( $invoice->generate_key( 'renewal_' ) );
942
		$invoice->set_number( '' );
943
		$invoice->set_completed_date( '' );
944
		$invoice->set_status( 'wpi-pending' );
945
		$invoice->recalculate_total();
946
		$invoice->save();
947
948
		return $invoice->get_id() ? $invoice : false;
949
    }
950
951
	/**
952
	 * Renews or completes a subscription
953
	 *
954
	 * @since  1.0.0
955
	 * @return int The subscription's id
956
	 */
957
	public function renew() {
958
959
		// Complete subscription if applicable
960
		if ( $this->is_last_renewal() ) {
961
			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...
962
		}
963
964
		// Calculate new expiration
965
		$frequency      = $this->get_frequency();
966
		$period         = $this->get_period();
967
		$new_expiration = strtotime( "+ $frequency $period", $this->get_expiration_time() );
968
969
		$this->set_expiration( date( 'Y-m-d H:i:s',$new_expiration ) );
970
		$this->set_status( 'active' );
971
		return $this->save();
972
973
		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...
974
975
	}
976
977
	/**
978
	 * Marks a subscription as completed
979
	 *
980
	 * Subscription is completed when the number of payments matches the billing_times field
981
	 *
982
	 * @since  1.0.0
983
	 * @return int|bool Subscription id or false if the subscription is cancelled.
984
	 */
985
	public function complete() {
986
987
		// Only mark a subscription as complete if it's not already cancelled.
988
		if ( $this->has_status( 'cancelled' ) ) {
989
			return false;
990
		}
991
992
		$this->set_status( 'completed' );
993
		return $this->save();
994
995
	}
996
997
	/**
998
	 * Marks a subscription as expired
999
	 *
1000
	 * @since  1.0.0
1001
	 * @param  bool $check_expiration
1002
	 * @return int|bool Subscription id or false if $check_expiration is true and expiration date is in the future.
1003
	 */
1004
	public function expire( $check_expiration = false ) {
1005
1006
		if ( $check_expiration && $this->get_expiration_time() > current_time( 'timestamp' ) ) {
1007
			// Do not mark as expired since real expiration date is in the future
1008
			return false;
1009
		}
1010
1011
		$this->set_status( 'expired' );
1012
		return $this->save();
1013
1014
	}
1015
1016
	/**
1017
	 * Marks a subscription as failing
1018
	 *
1019
	 * @since  2.4.2
1020
	 * @return int Subscription id.
1021
	 */
1022
	public function failing() {
1023
		$this->set_status( 'failing' );
1024
		return $this->save();
1025
	}
1026
1027
    /**
1028
     * Marks a subscription as cancelled
1029
     *
1030
     * @since  1.0.0
1031
     * @return int Subscription id.
1032
     */
1033
    public function cancel() {
1034
		$this->set_status( 'cancelled' );
1035
		return $this->save();
1036
    }
1037
1038
	/**
1039
	 * Determines if a subscription can be cancelled both locally and with a payment processor.
1040
	 *
1041
	 * @since  1.0.0
1042
	 * @return bool
1043
	 */
1044
	public function can_cancel() {
1045
		return apply_filters( 'wpinv_subscription_can_cancel', $this->has_status( $this->get_cancellable_statuses() ), $this );
1046
	}
1047
1048
    /**
1049
     * Returns an array of subscription statuses that can be cancelled
1050
     *
1051
     * @access      public
1052
     * @since       1.0.0
1053
     * @return      array
1054
     */
1055
    public function get_cancellable_statuses() {
1056
        return apply_filters( 'wpinv_recurring_cancellable_statuses', array( 'active', 'trialling', 'failing' ) );
1057
    }
1058
1059
	/**
1060
	 * Retrieves the URL to cancel subscription
1061
	 *
1062
	 * @since  1.0.0
1063
	 * @return string
1064
	 */
1065
	public function get_cancel_url() {
1066
		$url = getpaid_get_authenticated_action_url( 'subscription_cancel', $this->get_view_url() );
1067
		return apply_filters( 'wpinv_subscription_cancel_url', $url, $this );
1068
	}
1069
1070
	/**
1071
	 * Retrieves the URL to view a subscription
1072
	 *
1073
	 * @since  1.0.19
1074
	 * @return string
1075
	 */
1076
	public function get_view_url() {
1077
		$url = add_query_arg( 'subscription', $this->get_id(), get_permalink( (int) wpinv_get_option( 'invoice_subscription_page' ) ) );
1078
		return apply_filters( 'getpaid_get_subscription_view_url', $url, $this );
1079
	}
1080
1081
	/**
1082
	 * Determines if subscription can be manually renewed
1083
	 *
1084
	 * This method is filtered by payment gateways in order to return true on subscriptions
1085
	 * that can be renewed manually
1086
	 *
1087
	 * @since  2.5
1088
	 * @return bool
1089
	 */
1090
	public function can_renew() {
1091
		return apply_filters( 'wpinv_subscription_can_renew', true, $this );
1092
	}
1093
1094
	/**
1095
	 * Retrieves the URL to renew a subscription
1096
	 *
1097
	 * @since  2.5
1098
	 * @return string
1099
	 */
1100
	public function get_renew_url() {
1101
		$url = wp_nonce_url( add_query_arg( array( 'getpaid-action' => 'renew_subscription', 'sub_id' => $this->get_id ) ), 'getpaid-nonce' );
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...
1102
		return apply_filters( 'wpinv_subscription_renew_url', $url, $this );
1103
	}
1104
1105
	/**
1106
	 * Determines if subscription can have their payment method updated
1107
	 *
1108
	 * @since  1.0.0
1109
	 * @return bool
1110
	 */
1111
	public function can_update() {
1112
		return apply_filters( 'wpinv_subscription_can_update', false, $this );
1113
	}
1114
1115
	/**
1116
	 * Retrieves the URL to update subscription
1117
	 *
1118
	 * @since  1.0.0
1119
	 * @return string
1120
	 */
1121
	public function get_update_url() {
1122
		$url = add_query_arg( array( 'action' => 'update', 'subscription_id' => $this->get_id() ) );
1123
		return apply_filters( 'wpinv_subscription_update_url', $url, $this );
1124
	}
1125
1126
	/**
1127
	 * Retrieves the subscription status label
1128
	 *
1129
	 * @since  1.0.0
1130
	 * @return string
1131
	 */
1132
	public function get_status_label() {
1133
		return getpaid_get_subscription_status_label( $this->get_status() );
1134
	}
1135
1136
	/**
1137
	 * Retrieves the subscription status class
1138
	 *
1139
	 * @since  1.0.19
1140
	 * @return string
1141
	 */
1142
	public function get_status_class() {
1143
		$statuses = getpaid_get_subscription_status_classes();
1144
		return isset( $statuses[ $this->get_status() ] ) ? $statuses[ $this->get_status() ] : 'text-white bg-secondary';
1145
	}
1146
1147
    /**
1148
     * Retrieves the subscription status label
1149
     *
1150
     * @since  1.0.0
1151
     * @return string
1152
     */
1153
    public function get_status_label_html() {
1154
1155
		$status_label = sanitize_text_field( $this->get_status_label() );
1156
		$class        = esc_attr( $this->get_status_class() );
1157
		$status       = sanitize_html_class( $this->get_status_label() );
1158
1159
		return "<span class='bsui'><span class='d-inline-block py-2 px-3 rounded $class $status'>$status_label</span></span>";
1160
    }
1161
1162
    /**
1163
     * Determines if a payment exists with the specified transaction ID
1164
     *
1165
     * @since  2.4
1166
     * @param  string $txn_id The transaction ID from the merchant processor
1167
     * @return bool
1168
     */
1169
    public function payment_exists( $txn_id = '' ) {
1170
		$invoice_id = WPInv_Invoice::get_invoice_id_by_field( $txn_id, 'transaction_id' );
1171
        return ! empty( $invoice_id );
1172
	}
1173
1174
	/**
1175
	 * Handle the status transition.
1176
	 */
1177
	protected function status_transition() {
1178
		$status_transition = $this->status_transition;
1179
1180
		// Reset status transition variable.
1181
		$this->status_transition = false;
1182
1183
		if ( $status_transition ) {
1184
			try {
1185
1186
				// Fire a hook for the status change.
1187
				do_action( 'wpinv_subscription_' . $status_transition['to'], $this->get_id(), $this, $status_transition );
1188
				do_action( 'getpaid_subscription_' . $status_transition['to'], $this, $status_transition );
1189
1190
				if ( ! empty( $status_transition['from'] ) ) {
1191
1192
					/* translators: 1: old subscription status 2: new subscription status */
1193
					$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'] ) );
1194
1195
					// Note the transition occurred.
1196
					$this->get_parent_payment()->add_note( $transition_note, false, false, true );
1197
1198
					// Fire another hook.
1199
					do_action( 'getpaid_subscription_status_' . $status_transition['from'] . '_to_' . $status_transition['to'], $this->get_id(), $this );
1200
					do_action( 'getpaid_subscription_status_changed', $this, $status_transition['from'], $status_transition['to'] );
1201
1202
				} else {
1203
					/* translators: %s: new invoice status */
1204
					$transition_note = sprintf( __( 'Subscription status set to %s.', 'invoicing' ), getpaid_get_subscription_status_label( $status_transition['to'] ) );
1205
1206
					// Note the transition occurred.
1207
					$this->get_parent_payment()->add_note( $transition_note, false, false, true );
1208
1209
				}
1210
			} catch ( Exception $e ) {
1211
				$this->get_parent_payment()->add_note( __( 'Error during subscription status transition.', 'invoicing' ) . ' ' . $e->getMessage() );
1212
			}
1213
		}
1214
1215
	}
1216
1217
	/**
1218
	 * Save data to the database.
1219
	 *
1220
	 * @since 1.0.19
1221
	 * @return int subscription ID
1222
	 */
1223
	public function save() {
1224
		parent::save();
1225
		$this->status_transition();
1226
		return $this->get_id();
1227
	}
1228
1229
	/**
1230
	 * Activates a subscription.
1231
	 *
1232
	 * @since 1.0.19
1233
	 * @return int subscription ID
1234
	 */
1235
	public function activate() {
1236
		$status = 'trialling' == $this->get_status() ? 'trialling' : 'active';
1237
		$this->set_status( $status );
1238
		return $this->save();
1239
	}
1240
1241
}
1242