Passed
Push — master ( 72d56b...acf0a7 )
by Brian
10:07
created

delete_invoice_subscriptions()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 7
c 0
b 0
f 0
nc 5
nop 1
dl 0
loc 14
rs 10
1
<?php
2
/**
3
 * Main Subscriptions class.
4
 *
5
 */
6
7
defined( 'ABSPATH' ) || exit;
8
/**
9
 * Main Subscriptions class.
10
 *
11
 */
12
class WPInv_Subscriptions {
13
14
    /**
15
	 * Class constructor.
16
	 */
17
    public function __construct(){
18
19
        // Fire gateway specific hooks when a subscription changes.
20
        add_action( 'getpaid_subscription_status_changed', array( $this, 'process_subscription_status_change' ), 10, 3 );
21
22
        // De-activate a subscription whenever the invoice changes payment statuses.
23
        add_action( 'getpaid_invoice_status_wpi-refunded', array( $this, 'maybe_deactivate_invoice_subscription' ), 20 );
24
        add_action( 'getpaid_invoice_status_wpi-failed', array( $this, 'maybe_deactivate_invoice_subscription' ), 20 );
25
        add_action( 'getpaid_invoice_status_wpi-cancelled', array( $this, 'maybe_deactivate_invoice_subscription' ), 20 );
26
        add_action( 'getpaid_invoice_status_wpi-pending', array( $this, 'maybe_deactivate_invoice_subscription' ), 20 );
27
28
        // Handles subscription cancelations.
29
        add_action( 'getpaid_authenticated_action_subscription_cancel', array( $this, 'user_cancel_single_subscription' ) );
30
31
        // Create a subscription whenever an invoice is created, (and update it when it is updated).
32
        add_action( 'getpaid_new_invoice', array( $this, 'maybe_create_invoice_subscription' ), 5 );
33
        add_action( 'getpaid_update_invoice', array( $this, 'maybe_update_invoice_subscription' ), 5 );
34
35
        // Handles admin subscription update actions.
36
        add_action( 'getpaid_authenticated_admin_action_update_single_subscription', array( $this, 'admin_update_single_subscription' ) );
37
        add_action( 'getpaid_authenticated_admin_action_subscription_manual_renew', array( $this, 'admin_renew_single_subscription' ) );
38
        add_action( 'getpaid_authenticated_admin_action_subscription_manual_delete', array( $this, 'admin_delete_single_subscription' ) );
39
    
40
        // Filter invoice item row actions.
41
        add_action( 'getpaid-invoice-page-line-item-actions', array( $this, 'filter_invoice_line_item_actions' ), 10, 3 );
42
    }
43
44
    /**
45
     * Returns an invoice's subscription.
46
     *
47
     * @param WPInv_Invoice $invoice
48
     * @return WPInv_Subscription|bool
49
     */
50
    public function get_invoice_subscription( $invoice ) {
51
        $subscription_id = $invoice->get_subscription_id();
52
53
        // Fallback to the parent invoice if the child invoice has no subscription id.
54
        if ( empty( $subscription_id ) && $invoice->is_renewal() ) {
55
            $subscription_id = $invoice->get_parent_payment()->get_subscription_id();
56
        }
57
58
        // Fetch the subscription.
59
        $subscription = new WPInv_Subscription( $subscription_id );
60
61
        // Return subscription or use a fallback for backwards compatibility.
62
        return $subscription->exists() ? $subscription : wpinv_get_invoice_subscription( $invoice );
63
    }
64
65
    /**
66
     * Deactivates the invoice subscription(s) whenever an invoice status changes.
67
     * 
68
     * @param WPInv_Invoice $invoice
69
     */
70
    public function maybe_deactivate_invoice_subscription( $invoice ) {
71
72
        $subscriptions = getpaid_get_invoice_subscriptions( $invoice );
73
74
        if ( empty( $subscriptions ) ) {
75
            return;
76
        }
77
78
        if ( ! is_array( $subscriptions ) ) {
0 ignored issues
show
introduced by
The condition is_array($subscriptions) is always false.
Loading history...
79
            $subscriptions = array( $subscriptions );
80
        }
81
82
        foreach ( $subscriptions as $subscription ) {
83
            if ( $subscription->is_active() ) {
84
                $subscription->set_status( 'pending' );
85
                $subscription->save();
86
            }
87
        }
88
89
    }
90
91
    /**
92
	 * Processes subscription status changes.
93
     * 
94
     * @param WPInv_Subscription $subscription
95
     * @param string $from
96
     * @param string $to
97
	 */
98
    public function process_subscription_status_change( $subscription, $from, $to ) {
99
100
        $gateway = $subscription->get_gateway();
101
102
        if ( ! empty( $gateway ) ) {
103
            $gateway = sanitize_key( $gateway );
104
            $from    = sanitize_key( $from );
105
            $to      = sanitize_key( $to );
106
            do_action( "getpaid_{$gateway}_subscription_$to", $subscription, $from );
107
        }
108
109
    }
110
111
    /**
112
     * Get pretty subscription frequency
113
     *
114
     * @param $period
115
     * @param int $frequency_count The frequency of the period.
116
     * @deprecated
117
     * @return mixed|string|void
118
     */
119
    public static function wpinv_get_pretty_subscription_frequency( $period, $frequency_count = 1 ) {
120
        return getpaid_get_subscription_period_label( $period, $frequency_count );
121
    }
122
123
    /**
124
     * Handles cancellation requests for a subscription
125
     *
126
     * @access      public
127
     * @since       1.0.0
128
     * @return      void
129
     */
130
    public function user_cancel_single_subscription( $data ) {
131
132
        // Ensure there is a subscription to cancel.
133
        if ( empty( $data['subscription'] ) ) {
134
            return;
135
        }
136
137
        $subscription = new WPInv_Subscription( (int) $data['subscription'] );
138
139
        // Ensure that it exists and that it belongs to the current user.
140
        if ( ! $subscription->exists() || $subscription->get_customer_id() != get_current_user_id() ) {
141
            wpinv_set_error( 'invalid_subscription', __( 'You do not have permission to cancel this subscription', 'invoicing' ) );
142
143
        // Can it be cancelled.
144
        } else if ( ! $subscription->can_cancel() ) {
145
            wpinv_set_error( 'cannot_cancel', __( 'This subscription cannot be cancelled as it is not active.', 'invoicing' ) );
146
147
        // Cancel it.
148
        } else {
149
150
            $subscription->cancel();
151
            wpinv_set_error( 'cancelled', __( 'This subscription has been cancelled.', 'invoicing' ), 'info' );
152
        }
153
154
        $redirect = remove_query_arg( array( 'getpaid-action', 'getpaid-nonce' ) );
155
156
        wp_safe_redirect( $redirect );
157
        exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
158
159
    }
160
161
    /**
162
     * Creates a subscription(s) for an invoice.
163
     *
164
     * @access      public
165
     * @param       WPInv_Invoice $invoice
166
     * @since       1.0.0
167
     */
168
    public function maybe_create_invoice_subscription( $invoice ) {
169
        global $getpaid_subscriptions_skip_invoice_update;
170
171
        // Abort if it is not recurring.
172
        if ( ! $invoice->is_type( 'invoice' ) || $invoice->is_free() || ! $invoice->is_recurring() || $invoice->is_renewal() ) {
173
            return;
174
        }
175
176
        // Either group the subscriptions or only process a single suscription.
177
        if ( getpaid_should_group_subscriptions( $invoice ) ) {
178
179
            $subscription_groups = array();
180
            $is_first            = true;
181
182
            foreach ( getpaid_calculate_subscription_totals( $invoice ) as $group_key => $totals ) {
183
                $subscription_groups[ $group_key ] = $this->create_invoice_subscription_group( $totals, $invoice, 0, $is_first );
184
185
                if ( $is_first ) {
186
                    $getpaid_subscriptions_skip_invoice_update = true;
187
                    $invoice->set_subscription_id( $subscription_groups[ $group_key ]['subscription_id'] );
188
                    $invoice->save();
189
                    $getpaid_subscriptions_skip_invoice_update = false;
190
                }
191
    
192
                $is_first                          = false;
193
            }
194
195
            // Cache subscription groups.
196
            update_post_meta( $invoice->get_id(), 'getpaid_subscription_groups', $subscription_groups );
197
            return true;
198
199
        }
200
201
        $subscription = new WPInv_Subscription();
202
        return $this->update_invoice_subscription( $subscription, $invoice );
203
204
    }
205
206
    /**
207
     * Saves a new invoice subscription group.
208
     *
209
     * @access      public
210
     * @param       array $totals
211
     * @param       WPInv_Invoice $invoice
212
     * @param       int $subscription_id Current subscription id of the group.
213
     * @param       bool $is_first Whether or not this is the first subscription group for the invoice. In which case we'll add totals of non-recurring items.
214
     *
215
     * @since       2.3.0
216
     */
217
    public function create_invoice_subscription_group( $totals, $invoice, $subscription_id = 0, $is_first = false ) {
218
219
        $subscription = new WPInv_Subscription( (int) $subscription_id );
220
        $initial_amt  = $totals['initial_total'];
221
222
        // Maybe add non-recurring items.
223
        if ( $is_first ) {
224
            foreach ( $invoice->get_items() as $item ) {
225
                if ( ! $item->is_recurring() ) {
226
                    $initial_amt += $item->get_sub_total();
227
                }
228
            }
229
        }
230
231
        $subscription->set_customer_id( $invoice->get_customer_id() );
232
        $subscription->set_parent_invoice_id( $invoice->get_id() );
233
        $subscription->set_initial_amount( $initial_amt );
234
        $subscription->set_recurring_amount( $totals['recurring_total'] );
235
        $subscription->set_date_created( current_time( 'mysql' ) );
236
        $subscription->set_status( $invoice->is_paid() ? 'active' : 'pending' );
237
        $subscription->set_product_id( $totals['item_id'] );
238
        $subscription->set_period( $totals['period'] );
239
        $subscription->set_frequency( $totals['interval'] );
240
        $subscription->set_bill_times( $totals['recurring_limit'] );
241
        $subscription->set_next_renewal_date( $totals['renews_on'] );
242
243
        // Trial periods.
244
        if ( ! empty( $totals['trialling'] ) ) {
245
            $subscription->set_trial_period( $totals['trialling'] );
246
            $subscription->set_status( 'trialling' );
247
248
        // If initial amount is free, treat it as a free trial even if the subscription item does not have a free trial.
249
        } else if ( empty( $totals['initial_total'] ) ) {
250
            $subscription->set_trial_period( $totals['interval'] . ' ' . $totals['period'] );
251
            $subscription->set_status( 'trialling' );
252
        }
253
254
        $subscription->save();
255
256
        $totals['subscription_id'] = $subscription->get_id();
257
258
        return $totals;
259
    }
260
261
    /**
262
     * (Maybe) Updates a subscription for an invoice.
263
     *
264
     * @access      public
265
     * @param       WPInv_Invoice $invoice
266
     * @since       1.0.19
267
     */
268
    public function maybe_update_invoice_subscription( $invoice ) {
269
        global $getpaid_subscriptions_skip_invoice_update;
270
271
        // Avoid infinite loops.
272
        if ( ! empty( $getpaid_subscriptions_skip_invoice_update ) ) {
273
            return;
274
        }
275
276
        // Do not process renewals.
277
        if ( $invoice->is_renewal() ) {
278
            return;
279
        }
280
281
        // Delete existing subscriptions if available and the invoice is not recurring.
282
        if ( ! $invoice->is_recurring() ) {
283
            $this->delete_invoice_subscriptions( $invoice );
284
            return;
285
        }
286
287
        // Fetch existing subscriptions.
288
        $subscriptions = getpaid_get_invoice_subscriptions( $invoice );
289
290
        // Create new ones if no existing subscriptions.
291
        if ( empty( $subscriptions ) ) {
292
            return $this->maybe_create_invoice_subscription( $invoice );
293
        }
294
295
        // Abort if an invoice is paid and already has a subscription.
296
        if ( $invoice->is_paid() || $invoice->is_refunded() ) {
297
            return;
298
        }
299
300
        $is_grouped   = is_array( $subscriptions );
301
        $should_group = getpaid_should_group_subscriptions( $invoice );
302
303
        // Ensure that the subscriptions are only grouped if there are more than 1 recurring items.
304
        if ( $is_grouped != $should_group ) {
305
            $this->delete_invoice_subscriptions( $invoice );
306
            delete_post_meta( $invoice->get_id(), 'getpaid_subscription_groups' );
307
            return $this->maybe_create_invoice_subscription( $invoice );
308
        }
309
310
        // If there is only one recurring item...
311
        if ( ! $is_grouped ) {
312
            return $this->update_invoice_subscription( $subscriptions, $invoice );
313
        }
314
315
        // Process subscription groups.
316
        $current_groups      = getpaid_get_invoice_subscription_groups( $invoice );
317
        $subscription_groups = array();
318
        $is_first            = true;
319
320
        // Create new subscription groups.
321
        foreach ( getpaid_calculate_subscription_totals( $invoice ) as $group_key => $totals ) {
322
            $subscription_id                   = isset( $current_groups[ $group_key ] ) ? $current_groups[ $group_key ]['subscription_id'] : 0;
323
            $subscription_groups[ $group_key ] = $this->create_invoice_subscription_group( $totals, $invoice, $subscription_id, $is_first );
324
325
            if ( $is_first && $invoice->get_subscription_id() !== $subscription_groups[ $group_key ]['subscription_id'] ) {
326
                $getpaid_subscriptions_skip_invoice_update = true;
327
                $invoice->set_subscription_id( $subscription_groups[ $group_key ]['subscription_id'] );
328
                $invoice->save();
329
                $getpaid_subscriptions_skip_invoice_update = false;
330
            }
331
332
            $is_first                          = false;
333
        }
334
335
        // Delete non-existent subscription groups.
336
        foreach ( $current_groups as $group_key => $data ) {
337
            if ( ! isset( $subscription_groups[ $group_key ] ) ) {
338
                $subscription = new WPInv_Subscription( (int) $data['subscription_id'] );
339
340
                if ( $subscription->exists() ) {
341
                    $subscription->delete( true );
342
                }
343
344
            }
345
        }
346
347
        // Cache subscription groups.
348
        update_post_meta( $invoice->get_id(), 'getpaid_subscription_groups', $subscription_groups );
349
        return true;
350
351
    }
352
353
    /**
354
     * Deletes invoice subscription(s).
355
     *
356
     * @param WPInv_Invoice $invoice
357
     */
358
    public function delete_invoice_subscriptions( $invoice ) {
359
360
        $subscriptions = getpaid_get_invoice_subscriptions( $invoice );
361
362
        if ( empty( $subscriptions ) ) {
363
            return;
364
        }
365
366
        if ( ! is_array( $subscriptions ) ) {
0 ignored issues
show
introduced by
The condition is_array($subscriptions) is always false.
Loading history...
367
            $subscriptions = array( $subscriptions );
368
        }
369
370
        foreach ( $subscriptions as $subscription ) {
371
            $subscription->delete( true );
372
        }
373
374
    }
375
376
    /**
377
     * Updates a subscription for an invoice.
378
     *
379
     * @access      public
380
     * @param       WPInv_Subscription $subscription
381
     * @param       WPInv_Invoice $invoice
382
     * @since       1.0.19
383
     */
384
    public function update_invoice_subscription( $subscription, $invoice ) {
385
386
        // Delete the subscription if an invoice is free or nolonger recurring.
387
        if ( ! $invoice->is_type( 'invoice' ) || $invoice->is_free() || ! $invoice->is_recurring() ) {
388
            return $subscription->delete();
389
        }
390
391
        $subscription->set_customer_id( $invoice->get_customer_id() );
392
        $subscription->set_parent_invoice_id( $invoice->get_id() );
393
        $subscription->set_initial_amount( $invoice->get_initial_total() );
394
        $subscription->set_recurring_amount( $invoice->get_recurring_total() );
395
        $subscription->set_date_created( current_time( 'mysql' ) );
396
        $subscription->set_status( $invoice->is_paid() ? 'active' : 'pending' );
397
398
        // Get the recurring item and abort if it does not exist.
399
        $subscription_item = $invoice->get_recurring( true );
400
        if ( ! $subscription_item->get_id() ) {
401
            $invoice->set_subscription_id(0);
402
            $invoice->save();
403
            return $subscription->delete();
404
        }
405
406
        $subscription->set_product_id( $subscription_item->get_id() );
407
        $subscription->set_period( $subscription_item->get_recurring_period( true ) );
408
        $subscription->set_frequency( $subscription_item->get_recurring_interval() );
409
        $subscription->set_bill_times( $subscription_item->get_recurring_limit() );
410
411
        // Calculate the next renewal date.
412
        $period       = $subscription_item->get_recurring_period( true );
413
        $interval     = $subscription_item->get_recurring_interval();
414
415
        // If the subscription item has a trial period...
416
        if ( $subscription_item->has_free_trial() ) {
417
            $period   = $subscription_item->get_trial_period( true );
418
            $interval = $subscription_item->get_trial_interval();
419
            $subscription->set_trial_period( $interval . ' ' . $period );
420
            $subscription->set_status( 'trialling' );
421
        }
422
423
        // If initial amount is free, treat it as a free trial even if the subscription item does not have a free trial.
424
        if ( $invoice->has_free_trial() ) {
425
            $subscription->set_trial_period( $interval . ' ' . $period );
426
            $subscription->set_status( 'trialling' );
427
        }
428
429
        // Calculate the next renewal date.
430
        $expiration = date( 'Y-m-d H:i:s', strtotime( "+$interval $period", strtotime( $subscription->get_date_created() ) ) );
431
432
        $subscription->set_next_renewal_date( $expiration );
433
        $subscription->save();
434
        $invoice->set_subscription_id( $subscription->get_id() );
435
        return $subscription->get_id();
436
437
    }
438
439
    /**
440
     * Fired when an admin updates a subscription via the single subscription single page.
441
     *
442
     * @param       array $data
443
     * @since       1.0.19
444
     */
445
    public function admin_update_single_subscription( $args ) {
446
447
        // Ensure the subscription exists and that a status has been given.
448
        if ( empty( $args['subscription_id'] ) ) {
449
            return;
450
        }
451
452
        // Retrieve the subscriptions.
453
        $subscription = new WPInv_Subscription( $args['subscription_id'] );
454
455
        if ( $subscription->get_id() ) {
456
457
            $subscription->set_props(
458
                array(
459
                    'status'     => isset( $args['subscription_status'] ) ? $args['subscription_status'] : null,
460
                    'profile_id' => isset( $args['wpinv_subscription_profile_id'] ) ? $args['wpinv_subscription_profile_id'] : null,
461
                )
462
            );
463
464
            $subscription->save();
465
            getpaid_admin()->show_info( __( 'Subscription updated', 'invoicing' ) );
466
467
        }
468
469
    }
470
471
    /**
472
     * Fired when an admin manually renews a subscription.
473
     *
474
     * @param       array $data
475
     * @since       1.0.19
476
     */
477
    public function admin_renew_single_subscription( $args ) {
478
479
        // Ensure the subscription exists and that a status has been given.
480
        if ( empty( $args['id'] ) ) {
481
            return;
482
        }
483
484
        // Retrieve the subscriptions.
485
        $subscription = new WPInv_Subscription( $args['id'] );
486
487
        if ( $subscription->get_id() ) {
488
489
            do_action( 'getpaid_admin_renew_subscription', $subscription );
490
491
            $args = array( 'transaction_id', $subscription->get_parent_invoice()->generate_key( 'renewal_' ) );
492
493
            if ( ! $subscription->add_payment( $args ) ) {
494
                getpaid_admin()->show_error( __( 'We are unable to renew this subscription as the parent invoice does not exist.', 'invoicing' ) );
495
            } else {
496
                $subscription->renew();
497
                getpaid_admin()->show_info( __( 'This subscription has been renewed and extended.', 'invoicing' ) );
498
            } 
499
500
            wp_safe_redirect(
501
                add_query_arg(
502
                    array(
503
                        'getpaid-admin-action' => false,
504
                        'getpaid-nonce'        => false,
505
                    )
506
                )
507
            );
508
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
509
510
        }
511
512
    }
513
514
    /**
515
     * Fired when an admin manually deletes a subscription.
516
     *
517
     * @param       array $data
518
     * @since       1.0.19
519
     */
520
    public function admin_delete_single_subscription( $args ) {
521
522
        // Ensure the subscription exists and that a status has been given.
523
        if ( empty( $args['id'] ) ) {
524
            return;
525
        }
526
527
        // Retrieve the subscriptions.
528
        $subscription = new WPInv_Subscription( $args['id'] );
529
530
        if ( $subscription->delete() ) {
531
            getpaid_admin()->show_info( __( 'This subscription has been deleted.', 'invoicing' ) );
532
        } else {
533
            getpaid_admin()->show_error( __( 'We are unable to delete this subscription. Please try again.', 'invoicing' ) );
534
        }
535
    
536
        $redirected = wp_safe_redirect(
537
            add_query_arg(
538
                array(
539
                    'getpaid-admin-action' => false,
540
                    'getpaid-nonce'        => false,
541
                    'id'                   => false,
542
                )
543
            )
544
        );
545
546
        if ( $redirected ) {
547
            exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
548
        }
549
550
    }
551
552
    /**
553
     * Filters the invoice line items actions.
554
     *
555
     * @param array actions
0 ignored issues
show
Bug introduced by
The type actions was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
556
     * @param WPInv_Item $item
557
     * @param WPInv_Invoice $invoice
558
     */
559
    public function filter_invoice_line_item_actions( $actions, $item, $invoice ) {
560
561
        // Fetch item subscription.
562
        $args  = array(
563
            'invoice_in'  => $invoice->is_parent() ? $invoice->get_id() : $invoice->get_parent_id(),
564
            'product_in'  => $item->get_id(),
565
            'number'      => 1,
566
            'count_total' => false,
567
            'fields'      => 'id',
568
        );
569
570
        $subscription = new GetPaid_Subscriptions_Query( $args );
571
        $subscription = $subscription->get_results();
572
573
        // In case we found a match...
574
        if ( ! empty( $subscription ) ) {
575
            $url                     = esc_url( add_query_arg( 'subscription', (int) $subscription[0], get_permalink( (int) wpinv_get_option( 'invoice_subscription_page' ) ) ) );
576
            $actions['subscription'] = "<a href='$url' class='text-decoration-none'>" . __( 'Manage Subscription', 'invoicing' ) . '</a>';
577
        }
578
579
        return $actions;
580
581
    }
582
583
}
584