Passed
Push — master ( dd5891...4a2524 )
by Brian
04:39
created

admin_update_single_subscription()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 25
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 13
nc 3
nop 1
dl 0
loc 25
rs 8.8333
c 0
b 0
f 0
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( 'wpinv_invoice_metabox_saved', array( $this, 'maybe_update_invoice_subscription' ), 5 );
33
        add_action( 'getpaid_checkout_invoice_updated', 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
            $notice = 'perm_cancel_subscription';
142
143
        // Can it be cancelled.
144
        } elseif ( ! $subscription->can_cancel() ) {
145
            $notice = 'cannot_cancel_subscription';
146
147
        // Cancel it.
148
        } else {
149
150
            $subscription->cancel();
151
            $notice = 'cancelled_subscription';
152
        }
153
154
        $redirect = array(
155
            'getpaid-action' => false,
156
            'getpaid-nonce'  => false,
157
            'wpinv-notice'   => $notice,
158
        );
159
160
        wp_safe_redirect( add_query_arg( $redirect ) );
161
        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...
162
163
    }
164
165
    /**
166
     * Creates a subscription(s) for an invoice.
167
     *
168
     * @access      public
169
     * @param       WPInv_Invoice $invoice
170
     * @since       1.0.0
171
     */
172
    public function maybe_create_invoice_subscription( $invoice ) {
173
        global $getpaid_subscriptions_skip_invoice_update;
174
175
        // Abort if it is not recurring.
176
        if ( ! $invoice->is_type( 'invoice' ) || $invoice->is_free() || ! $invoice->is_recurring() || $invoice->is_renewal() ) {
177
            return;
178
        }
179
180
        // Either group the subscriptions or only process a single suscription.
181
        if ( getpaid_should_group_subscriptions( $invoice ) ) {
182
183
            $subscription_groups = array();
184
            $is_first            = true;
185
186
            foreach ( getpaid_calculate_subscription_totals( $invoice ) as $group_key => $totals ) {
187
                $subscription_groups[ $group_key ] = $this->create_invoice_subscription_group( $totals, $invoice, 0, $is_first );
188
189
                if ( $is_first ) {
190
                    $getpaid_subscriptions_skip_invoice_update = true;
191
                    $invoice->set_subscription_id( $subscription_groups[ $group_key ]['subscription_id'] );
192
                    $invoice->save();
193
                    $getpaid_subscriptions_skip_invoice_update = false;
194
                }
195
196
                $is_first                          = false;
197
            }
198
199
            // Cache subscription groups.
200
            update_post_meta( $invoice->get_id(), 'getpaid_subscription_groups', $subscription_groups );
201
            return true;
202
203
        }
204
205
        $subscription = new WPInv_Subscription();
206
        return $this->update_invoice_subscription( $subscription, $invoice );
207
208
    }
209
210
    /**
211
     * Saves a new invoice subscription group.
212
     *
213
     * @access      public
214
     * @param       array $totals
215
     * @param       WPInv_Invoice $invoice
216
     * @param       int $subscription_id Current subscription id of the group.
217
     * @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.
218
     *
219
     * @since       2.3.0
220
     */
221
    public function create_invoice_subscription_group( $totals, $invoice, $subscription_id = 0, $is_first = false ) {
222
223
        $subscription  = new WPInv_Subscription( (int) $subscription_id );
224
        $initial_amt   = $totals['initial_total'];
225
        $recurring_amt = $totals['recurring_total'];
226
        $fees          = array();
227
228
        // Maybe add recurring fees.
229
        if ( $is_first ) {
230
231
            foreach ( $invoice->get_fees() as $i => $fee ) {
232
                if ( ! empty( $fee['recurring_fee'] ) ) {
233
                    $initial_amt   += wpinv_sanitize_amount( $fee['initial_fee'] );
234
                    $recurring_amt += wpinv_sanitize_amount( $fee['recurring_fee'] );
235
                    $fees[ $i ]       = $fee;
236
                }
237
            }
238
        }
239
240
        $subscription->set_customer_id( $invoice->get_customer_id() );
241
        $subscription->set_parent_invoice_id( $invoice->get_id() );
242
        $subscription->set_initial_amount( $initial_amt );
243
        $subscription->set_recurring_amount( $recurring_amt );
244
        $subscription->set_date_created( current_time( 'mysql' ) );
245
        $subscription->set_status( $invoice->is_paid() ? 'active' : 'pending' );
246
        $subscription->set_product_id( $totals['item_id'] );
247
        $subscription->set_period( $totals['period'] );
248
        $subscription->set_frequency( $totals['interval'] );
249
        $subscription->set_bill_times( $totals['recurring_limit'] );
250
        $subscription->set_next_renewal_date( $totals['renews_on'] );
251
252
        // Trial periods.
253
        if ( ! empty( $totals['trialling'] ) ) {
254
            $subscription->set_trial_period( $totals['trialling'] );
255
            $subscription->set_status( 'trialling' );
256
257
        // If initial amount is free, treat it as a free trial even if the subscription item does not have a free trial.
258
        } elseif ( empty( $initial_amt ) ) {
259
            $subscription->set_trial_period( $totals['interval'] . ' ' . $totals['period'] );
260
            $subscription->set_status( 'trialling' );
261
        }
262
263
        $subscription->save();
264
265
        $totals['subscription_id'] = $subscription->get_id();
266
        $totals['fees']            = $fees;
267
268
        return $totals;
269
    }
270
271
    /**
272
     * (Maybe) Updates a subscription for an invoice.
273
     *
274
     * @access      public
275
     * @param       WPInv_Invoice $invoice
276
     * @since       1.0.19
277
     */
278
    public function maybe_update_invoice_subscription( $invoice ) {
279
        global $getpaid_subscriptions_skip_invoice_update;
280
281
        // Avoid infinite loops.
282
        if ( ! empty( $getpaid_subscriptions_skip_invoice_update ) ) {
283
            return;
284
        }
285
286
        // Do not process renewals.
287
        if ( $invoice->is_renewal() ) {
288
            return;
289
        }
290
291
        // Delete existing subscriptions if available and the invoice is not recurring.
292
        if ( ! $invoice->is_recurring() ) {
293
            $this->delete_invoice_subscriptions( $invoice );
294
            return;
295
        }
296
297
        // Fetch existing subscriptions.
298
        $subscriptions = getpaid_get_invoice_subscriptions( $invoice );
299
300
        // Create new ones if no existing subscriptions.
301
        if ( empty( $subscriptions ) ) {
302
            return $this->maybe_create_invoice_subscription( $invoice );
303
        }
304
305
        // Abort if an invoice is paid and already has a subscription.
306
        if ( $invoice->is_paid() || $invoice->is_refunded() ) {
307
            return;
308
        }
309
310
        $is_grouped   = is_array( $subscriptions );
311
        $should_group = getpaid_should_group_subscriptions( $invoice );
312
313
        // Ensure that the subscriptions are only grouped if there are more than 1 recurring items.
314
        if ( $is_grouped != $should_group ) {
315
            $this->delete_invoice_subscriptions( $invoice );
316
            delete_post_meta( $invoice->get_id(), 'getpaid_subscription_groups' );
317
            return $this->maybe_create_invoice_subscription( $invoice );
318
        }
319
320
        // If there is only one recurring item...
321
        if ( ! $is_grouped ) {
322
            return $this->update_invoice_subscription( $subscriptions, $invoice );
323
        }
324
325
        // Process subscription groups.
326
        $current_groups      = getpaid_get_invoice_subscription_groups( $invoice->get_id() );
327
        $subscription_groups = array();
328
        $is_first            = true;
329
330
        // Create new subscription groups.
331
        foreach ( getpaid_calculate_subscription_totals( $invoice ) as $group_key => $totals ) {
332
            $subscription_id                   = isset( $current_groups[ $group_key ] ) ? $current_groups[ $group_key ]['subscription_id'] : 0;
333
            $subscription_groups[ $group_key ] = $this->create_invoice_subscription_group( $totals, $invoice, $subscription_id, $is_first );
334
335
            if ( $is_first && $invoice->get_subscription_id() !== $subscription_groups[ $group_key ]['subscription_id'] ) {
336
                $getpaid_subscriptions_skip_invoice_update = true;
337
                $invoice->set_subscription_id( $subscription_groups[ $group_key ]['subscription_id'] );
338
                $invoice->save();
339
                $getpaid_subscriptions_skip_invoice_update = false;
340
            }
341
342
            $is_first                          = false;
343
        }
344
345
        // Delete non-existent subscription groups.
346
        foreach ( $current_groups as $group_key => $data ) {
347
            if ( ! isset( $subscription_groups[ $group_key ] ) ) {
348
                $subscription = new WPInv_Subscription( (int) $data['subscription_id'] );
349
350
                if ( $subscription->exists() ) {
351
                    $subscription->delete( true );
352
                }
353
}
354
        }
355
356
        // Cache subscription groups.
357
        update_post_meta( $invoice->get_id(), 'getpaid_subscription_groups', $subscription_groups );
358
        return true;
359
360
    }
361
362
    /**
363
     * Deletes invoice subscription(s).
364
     *
365
     * @param WPInv_Invoice $invoice
366
     */
367
    public function delete_invoice_subscriptions( $invoice ) {
368
369
        $subscriptions = getpaid_get_invoice_subscriptions( $invoice );
370
371
        if ( empty( $subscriptions ) ) {
372
            return;
373
        }
374
375
        if ( ! is_array( $subscriptions ) ) {
0 ignored issues
show
introduced by
The condition is_array($subscriptions) is always false.
Loading history...
376
            $subscriptions = array( $subscriptions );
377
        }
378
379
        foreach ( $subscriptions as $subscription ) {
380
            $subscription->delete( true );
381
        }
382
383
    }
384
385
    /**
386
     * Updates a subscription for an invoice.
387
     *
388
     * @access      public
389
     * @param       WPInv_Subscription $subscription
390
     * @param       WPInv_Invoice $invoice
391
     * @since       1.0.19
392
     */
393
    public function update_invoice_subscription( $subscription, $invoice ) {
394
395
        // Delete the subscription if an invoice is free or nolonger recurring.
396
        if ( ! $invoice->is_type( 'invoice' ) || $invoice->is_free() || ! $invoice->is_recurring() ) {
397
            return $subscription->delete();
398
        }
399
400
        $subscription->set_customer_id( $invoice->get_customer_id() );
401
        $subscription->set_parent_invoice_id( $invoice->get_id() );
402
        $subscription->set_initial_amount( $invoice->get_initial_total() );
403
        $subscription->set_recurring_amount( $invoice->get_recurring_total() );
404
        $subscription->set_date_created( current_time( 'mysql' ) );
405
        $subscription->set_status( $invoice->is_paid() ? 'active' : 'pending' );
406
407
        // Get the recurring item and abort if it does not exist.
408
        $subscription_item = $invoice->get_recurring( true );
409
        if ( ! $subscription_item->get_id() ) {
410
            $invoice->set_subscription_id( 0 );
411
            $invoice->save();
412
            return $subscription->delete();
413
        }
414
415
        $subscription->set_product_id( $subscription_item->get_id() );
416
        $subscription->set_period( $subscription_item->get_recurring_period( true ) );
417
        $subscription->set_frequency( $subscription_item->get_recurring_interval() );
418
        $subscription->set_bill_times( $subscription_item->get_recurring_limit() );
419
420
        // Calculate the next renewal date.
421
        $period       = $subscription_item->get_recurring_period( true );
422
        $interval     = $subscription_item->get_recurring_interval();
423
424
        // If the subscription item has a trial period...
425
        if ( $subscription_item->has_free_trial() ) {
426
            $period   = $subscription_item->get_trial_period( true );
427
            $interval = $subscription_item->get_trial_interval();
428
            $subscription->set_trial_period( $interval . ' ' . $period );
429
            $subscription->set_status( 'trialling' );
430
        }
431
432
        // If initial amount is free, treat it as a free trial even if the subscription item does not have a free trial.
433
        if ( $invoice->has_free_trial() ) {
434
            $subscription->set_trial_period( $interval . ' ' . $period );
435
            $subscription->set_status( 'trialling' );
436
        }
437
438
        // Calculate the next renewal date.
439
        $expiration = date( 'Y-m-d H:i:s', strtotime( "+$interval $period", strtotime( $subscription->get_date_created() ) ) );
440
441
        $subscription->set_next_renewal_date( $expiration );
442
        $subscription->save();
443
        $invoice->set_subscription_id( $subscription->get_id() );
444
        return $subscription->get_id();
445
446
    }
447
448
    /**
449
     * Fired when an admin updates a subscription via the single subscription single page.
450
     *
451
     * @param       array $data
452
     * @since       1.0.19
453
     */
454
    public function admin_update_single_subscription( $args ) {
455
456
        // Ensure the subscription exists and that a status has been given.
457
        if ( empty( $args['subscription_id'] ) ) {
458
            return;
459
        }
460
461
        // Retrieve the subscriptions.
462
        $subscription = new WPInv_Subscription( $args['subscription_id'] );
463
464
        if ( $subscription->get_id() ) {
465
466
            $subscription->set_props(
467
                array(
468
                    'status'       => isset( $args['subscription_status'] ) ? $args['subscription_status'] : null,
469
                    'profile_id'   => isset( $args['wpinv_subscription_profile_id'] ) ? $args['wpinv_subscription_profile_id'] : null,
470
                    'date_created' => ! empty( $args['wpinv_subscription_date_created'] ) ? $args['wpinv_subscription_date_created'] : null,
471
                    'expiration'   => ! empty( $args['wpinv_subscription_expiration'] ) ? $args['wpinv_subscription_expiration'] : null,
472
                )
473
            );
474
475
            $subscription->save();
476
            getpaid_admin()->show_info( __( 'Subscription updated', 'invoicing' ) );
477
478
            do_action( 'getpaid_admin_updated_subscription', $subscription, $args );
479
        }
480
481
    }
482
483
    /**
484
     * Fired when an admin manually renews a subscription.
485
     *
486
     * @param       array $data
487
     * @since       1.0.19
488
     */
489
    public function admin_renew_single_subscription( $args ) {
490
491
        // Ensure the subscription exists and that a status has been given.
492
        if ( empty( $args['id'] ) ) {
493
            return;
494
        }
495
496
        // Retrieve the subscriptions.
497
        $subscription = new WPInv_Subscription( $args['id'] );
498
499
        if ( $subscription->get_id() ) {
500
501
            do_action( 'getpaid_admin_renew_subscription', $subscription );
502
503
            $args = array( 'transaction_id', $subscription->get_parent_invoice()->generate_key( 'renewal_' ) );
504
505
            if ( ! $subscription->add_payment( $args ) ) {
506
                getpaid_admin()->show_error( __( 'We are unable to renew this subscription as the parent invoice does not exist.', 'invoicing' ) );
507
            } else {
508
                $subscription->renew();
509
                getpaid_admin()->show_info( __( 'This subscription has been renewed and extended.', 'invoicing' ) );
510
            }
511
512
            wp_safe_redirect(
513
                add_query_arg(
514
                    array(
515
                        'getpaid-admin-action' => false,
516
                        'getpaid-nonce'        => false,
517
                    )
518
                )
519
            );
520
            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...
521
522
        }
523
524
    }
525
526
    /**
527
     * Fired when an admin manually deletes a subscription.
528
     *
529
     * @param       array $data
530
     * @since       1.0.19
531
     */
532
    public function admin_delete_single_subscription( $args ) {
533
534
        // Ensure the subscription exists and that a status has been given.
535
        if ( empty( $args['id'] ) ) {
536
            return;
537
        }
538
539
        // Retrieve the subscriptions.
540
        $subscription = new WPInv_Subscription( $args['id'] );
541
542
        if ( $subscription->delete() ) {
543
            getpaid_admin()->show_info( __( 'This subscription has been deleted.', 'invoicing' ) );
544
        } else {
545
            getpaid_admin()->show_error( __( 'We are unable to delete this subscription. Please try again.', 'invoicing' ) );
546
        }
547
548
        $redirected = wp_safe_redirect(
549
            add_query_arg(
550
                array(
551
                    'getpaid-admin-action' => false,
552
                    'getpaid-nonce'        => false,
553
                    'id'                   => false,
554
                )
555
            )
556
        );
557
558
        if ( $redirected ) {
559
            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...
560
        }
561
562
    }
563
564
    /**
565
     * Filters the invoice line items actions.
566
     *
567
     * @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...
568
     * @param WPInv_Item $item
569
     * @param WPInv_Invoice $invoice
570
     */
571
    public function filter_invoice_line_item_actions( $actions, $item, $invoice ) {
572
573
        // Abort if this invoice uses subscription groups.
574
        $subscriptions = getpaid_get_invoice_subscriptions( $invoice );
575
        if ( ! $invoice->is_recurring() || ! is_object( $subscriptions ) ) {
576
            return $actions;
577
        }
578
579
        // Fetch item subscription.
580
        $args  = array(
581
            'invoice_in'  => $invoice->is_parent() ? $invoice->get_id() : $invoice->get_parent_id(),
582
            'product_in'  => $item->get_id(),
583
            'number'      => 1,
584
            'count_total' => false,
585
            'fields'      => 'id',
586
        );
587
588
        $subscription = new GetPaid_Subscriptions_Query( $args );
589
        $subscription = $subscription->get_results();
590
591
        // In case we found a match...
592
        if ( ! empty( $subscription ) ) {
593
            $url                     = esc_url( add_query_arg( 'subscription', (int) $subscription[0], get_permalink( (int) wpinv_get_option( 'invoice_subscription_page' ) ) ) );
594
            $actions['subscription'] = "<a href='$url' class='text-decoration-none'>" . __( 'Manage Subscription', 'invoicing' ) . '</a>';
595
        }
596
597
        return $actions;
598
599
    }
600
601
}
602