Passed
Push — develop ( 8ebc76...5a0ded )
by Reüel
05:00 queued 17s
created

SubscriptionsModule::privacy_export()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 50
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 26
c 0
b 0
f 0
nc 9
nop 2
dl 0
loc 50
rs 8.6315
1
<?php
2
/**
3
 * Subscriptions Module
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2018 Pronamic
7
 * @license   GPL-3.0-or-later
8
 * @package   Pronamic\WordPress\Pay\Subscriptions
9
 */
10
11
namespace Pronamic\WordPress\Pay\Subscriptions;
12
13
use DateInterval;
14
use DatePeriod;
15
use Pronamic\WordPress\DateTime\DateTime;
16
use Pronamic\WordPress\DateTime\DateTimeZone;
17
use Pronamic\WordPress\Pay\Core\Gateway;
18
use Pronamic\WordPress\Pay\Core\Recurring;
19
use Pronamic\WordPress\Pay\Core\Server;
20
use Pronamic\WordPress\Pay\Core\Statuses;
21
use Pronamic\WordPress\Pay\Payments\Payment;
22
use Pronamic\WordPress\Pay\Plugin;
23
use WP_CLI;
0 ignored issues
show
Bug introduced by
The type WP_CLI 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...
24
use WP_Query;
25
26
/**
27
 * Title: Subscriptions module
28
 * Description:
29
 * Copyright: Copyright (c) 2005 - 2018
30
 * Company: Pronamic
31
 *
32
 * @see https://woocommerce.com/2017/04/woocommerce-3-0-release/
33
 * @see https://woocommerce.wordpress.com/2016/10/27/the-new-crud-classes-in-woocommerce-2-7/
34
 * @author Remco Tolsma
35
 * @version 3.7.0
36
 * @since 3.7.0
37
 */
38
class SubscriptionsModule {
39
	/**
40
	 * Plugin.
41
	 *
42
	 * @var Plugin $plugin
43
	 */
44
	public $plugin;
45
46
	/**
47
	 * Construct and initialize a subscriptions module object.
48
	 *
49
	 * @param Plugin $plugin The plugin.
50
	 */
51
	public function __construct( Plugin $plugin ) {
52
		$this->plugin = $plugin;
53
54
		// Actions.
55
		add_action( 'wp_loaded', array( $this, 'handle_subscription' ) );
56
57
		add_action( 'plugins_loaded', array( $this, 'maybe_schedule_subscription_payments' ), 5 );
58
59
		// Exclude subscription notes.
60
		add_filter( 'comments_clauses', array( $this, 'exclude_subscription_comment_notes' ), 10, 2 );
61
62
		add_action( 'pronamic_pay_new_payment', array( $this, 'maybe_create_subscription' ) );
63
64
		// The 'pronamic_pay_update_subscription_payments' hook adds subscription payments and sends renewal notices.
65
		add_action( 'pronamic_pay_update_subscription_payments', array( $this, 'update_subscription_payments' ) );
66
67
		// Listen to payment status changes so we can update related subscriptions.
68
		add_action( 'pronamic_payment_status_update', array( $this, 'payment_status_update' ) );
69
70
		// Listen to subscription status changes so we can log these in a note.
71
		add_action( 'pronamic_subscription_status_update', array( $this, 'log_subscription_status_update' ), 10, 4 );
72
73
		// WordPress CLI.
74
		// @see https://github.com/woocommerce/woocommerce/blob/3.3.1/includes/class-woocommerce.php#L365-L369.
75
		// @see https://github.com/woocommerce/woocommerce/blob/3.3.1/includes/class-wc-cli.php.
76
		// @see https://make.wordpress.org/cli/handbook/commands-cookbook/.
77
		if ( defined( 'WP_CLI' ) && WP_CLI ) {
0 ignored issues
show
Bug introduced by
The constant Pronamic\WordPress\Pay\Subscriptions\WP_CLI was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
78
			WP_CLI::add_command( 'pay subscriptions test', array( $this, 'cli_subscriptions_test' ) );
79
		}
80
	}
81
82
	/**
83
	 * Handle subscription actions.
84
	 *
85
	 * Extensions like Gravity Forms can send action links in for example
86
	 * email notifications so users can cancel or renew their subscription.
87
	 */
88
	public function handle_subscription() {
89
		if ( ! filter_has_var( INPUT_GET, 'subscription' ) ) {
90
			return;
91
		}
92
93
		if ( ! filter_has_var( INPUT_GET, 'action' ) ) {
94
			return;
95
		}
96
97
		if ( ! filter_has_var( INPUT_GET, 'key' ) ) {
98
			return;
99
		}
100
101
		// @see https://github.com/woothemes/woocommerce/blob/2.3.11/includes/class-wc-cache-helper.php
102
		// @see https://www.w3-edge.com/products/w3-total-cache/
103
		if ( ! defined( 'DONOTCACHEPAGE' ) ) {
104
			define( 'DONOTCACHEPAGE', true );
105
		}
106
107
		if ( ! defined( 'DONOTCACHEDB' ) ) {
108
			define( 'DONOTCACHEDB', true );
109
		}
110
111
		if ( ! defined( 'DONOTMINIFY' ) ) {
112
			define( 'DONOTMINIFY', true );
113
		}
114
115
		if ( ! defined( 'DONOTCDN' ) ) {
116
			define( 'DONOTCDN', true );
117
		}
118
119
		if ( ! defined( 'DONOTCACHEOBJECT' ) ) {
120
			define( 'DONOTCACHEOBJECT', true );
121
		}
122
123
		nocache_headers();
124
125
		$subscription_id = filter_input( INPUT_GET, 'subscription', FILTER_SANITIZE_STRING );
126
		$subscription    = get_pronamic_subscription( $subscription_id );
127
128
		$action = filter_input( INPUT_GET, 'action', FILTER_SANITIZE_STRING );
129
130
		$key = filter_input( INPUT_GET, 'key', FILTER_SANITIZE_STRING );
131
132
		// Check if subscription is valid.
133
		if ( ! $subscription ) {
134
			return;
135
		}
136
137
		// Check if subscription key is valid.
138
		if ( $key !== $subscription->get_key() ) {
139
			wp_redirect( home_url() );
140
141
			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...
142
		}
143
144
		// Check if we should redirect.
145
		$should_redirect = true;
146
147
		switch ( $action ) {
148
			case 'cancel':
149
				if ( Statuses::CANCELLED !== $subscription->get_status() ) {
150
					$subscription->set_status( Statuses::CANCELLED );
151
152
					$this->update_subscription( $subscription, $should_redirect );
153
				}
154
155
				break;
156
			case 'renew':
157
				$gateway = Plugin::get_gateway( $subscription->config_id );
158
159
				$html = null;
160
161
				if ( ! $gateway ) {
162
					$html = __( 'The subscription can not be renewed.', 'pronamic_ideal' );
163
				} elseif ( $gateway->supports( 'recurring' ) && Statuses::ACTIVE === $subscription->get_status() ) {
164
					$html = __( 'The subscription is already active.', 'pronamic_ideal' );
165
				} else {
166
					if ( 'POST' === Server::get( 'REQUEST_METHOD' ) ) {
167
						$renewal = array(
168
							'issuer' => filter_input( INPUT_POST, 'pronamic_ideal_issuer_id', FILTER_SANITIZE_STRING ),
169
						);
170
171
						$payment = $this->start_recurring( $subscription, $gateway, $renewal );
172
173
						$error = $gateway->get_error();
174
175
						if ( $gateway->has_error() && is_wp_error( $error ) ) {
176
							Plugin::render_errors( $error );
177
178
							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...
179
						}
180
181
						$gateway->redirect( $payment );
182
					}
183
184
					// Payment method input HTML.
185
					$gateway->set_payment_method( $subscription->payment_method );
186
187
					// Format subscription length.
188
					$length = $subscription->get_interval() . ' ';
189
190
					switch ( $subscription->get_interval_period() ) {
191
						case 'D':
192
							$length .= _n( 'day', 'days', $subscription->get_interval(), 'pronamic_ideal' );
193
194
							break;
195
						case 'W':
196
							$length .= _n( 'week', 'weeks', $subscription->get_interval(), 'pronamic_ideal' );
197
198
							break;
199
						case 'M':
200
							$length .= _n( 'month', 'months', $subscription->get_interval(), 'pronamic_ideal' );
201
202
							break;
203
						case 'Y':
204
							$length .= _n( 'year', 'years', $subscription->get_interval(), 'pronamic_ideal' );
205
206
							break;
207
					}
208
209
					$form_inner = sprintf(
210
						'<h1>%14s</h1> <p>%2$s</p> <hr /> <p><strong>%3$s:</strong> %4$s</p> <p><strong>%5$s:</strong> %6$s</p>',
211
						esc_html__( 'Subscription Renewal', 'pronamic_ideal' ),
212
						sprintf(
213
							__( 'The subscription epxires at %s.', 'pronamic_ideal' ),
214
							$subscription->get_expiry_date()->format_i18n()
215
						),
216
						esc_html__( 'Subscription length', 'pronamic_ideal' ),
217
						esc_html( $length ),
218
						esc_html__( 'Amount', 'pronamic_ideal' ),
219
						esc_html( $subscription->get_amount()->format_i18n() )
220
					);
221
222
					$form_inner .= $gateway->get_input_html();
223
224
					$form_inner .= sprintf(
225
						'<p><input class="pronamic-pay-btn" type="submit" name="pay" value="%s" /></p>',
226
						__( 'Pay', 'pronamic_ideal' )
227
					);
228
229
					$html = sprintf(
230
						'<form id="pronamic_ideal_form" name="pronamic_ideal_form" method="post">%s</form>',
231
						$form_inner
232
					);
233
				}
234
235
				require Plugin::$dirname . '/views/subscription-renew.php';
236
237
				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...
238
		}
239
	}
240
241
	/**
242
	 * Start a recurring payment at the specified gateway for the specified subscription.
243
	 *
244
	 * @param Subscription  $subscription The subscription to start a recurring payment for.
245
	 * @param Gateway       $gateway      The gateway to start the recurring payment at.
246
	 * @param boolean|array $renewal      Flag for renewal payment.
247
	 */
248
	public function start_recurring( Subscription $subscription, Gateway $gateway, $renewal = null ) {
249
		if ( null === $renewal ) {
250
			// If next payment date is after the subscription end date unset the next payment date.
251
			if ( isset( $subscription->end_date, $subscription->next_payment ) && $subscription->end_date <= $subscription->next_payment ) {
252
				$subscription->next_payment = null;
253
			}
254
255
			// If there is no next payment date change the subscription status to completed.
256
			if ( empty( $subscription->next_payment ) ) {
257
				$subscription->status      = Statuses::COMPLETED;
258
				$subscription->expiry_date = $subscription->end_date;
259
260
				$subscription->save();
261
262
				// @todo
263
				return;
264
			}
265
266
			if ( ! $gateway->supports( 'recurring' ) ) {
267
				return;
268
			}
269
		}
270
271
		// Calculate payment start and end dates.
272
		$start_date = new DateTime();
273
274
		if ( ! empty( $subscription->next_payment ) ) {
275
			$start_date = clone $subscription->next_payment;
276
		}
277
278
		$end_date = clone $start_date;
279
		$end_date->add( $subscription->get_date_interval() );
280
281
		$subscription->next_payment = $end_date;
282
283
		// Create follow up payment.
284
		$payment = new Payment();
285
286
		$payment->config_id        = $subscription->config_id;
287
		$payment->user_id          = $subscription->user_id;
0 ignored issues
show
Bug introduced by
The property user_id does not seem to exist on Pronamic\WordPress\Pay\Subscriptions\Subscription.
Loading history...
Bug introduced by
The property user_id does not exist on Pronamic\WordPress\Pay\Payments\Payment. Did you mean user_ip?
Loading history...
288
		$payment->source           = $subscription->source;
289
		$payment->source_id        = $subscription->source_id;
290
		$payment->description      = $subscription->description;
291
		$payment->order_id         = $subscription->order_id;
292
		$payment->email            = $subscription->email;
293
		$payment->customer_name    = $subscription->customer_name;
294
		$payment->address          = $subscription->address;
295
		$payment->address          = $subscription->address;
296
		$payment->city             = $subscription->city;
297
		$payment->zip              = $subscription->zip;
298
		$payment->country          = $subscription->country;
299
		$payment->telephone_number = $subscription->telephone_number;
300
		$payment->method           = $subscription->payment_method;
301
		$payment->subscription     = $subscription;
302
		$payment->subscription_id  = $subscription->get_id();
303
		$payment->start_date       = $start_date;
304
		$payment->end_date         = $end_date;
305
		$payment->recurring_type   = 'recurring';
306
		$payment->recurring        = true;
307
		$payment->set_amount( $subscription->get_amount() );
308
309
		// Handle renewals.
310
		if ( is_array( $renewal ) ) {
311
			$payment->recurring = false;
312
			$payment->issuer    = $renewal['issuer'];
313
		}
314
315
		// Start payment.
316
		$payment = Plugin::start_payment( $payment, $gateway );
317
318
		// Update subscription.
319
		$subscription->save();
320
321
		return $payment;
322
	}
323
324
	/**
325
	 * Update the specified subscription and redirect if allowed.
326
	 *
327
	 * @param Subscription $subscription The updated subscription.
328
	 * @param boolean      $can_redirect Flag to redirect or not.
329
	 */
330
	public function update_subscription( $subscription = null, $can_redirect = true ) {
331
		if ( empty( $subscription ) ) {
332
			return;
333
		}
334
335
		$subscription->save();
336
337
		if ( defined( 'DOING_CRON' ) && empty( $subscription->status ) ) {
338
			$can_redirect = false;
339
		}
340
341
		if ( $can_redirect ) {
342
			wp_redirect( home_url() );
343
344
			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...
345
		}
346
	}
347
348
	/**
349
	 * Comments clauses.
350
	 *
351
	 * @param array             $clauses The database query clauses.
352
	 * @param \WP_Comment_Query $query   The WordPress comment query object.
353
	 * @return array
354
	 */
355
	public function exclude_subscription_comment_notes( $clauses, $query ) {
356
		$type = $query->query_vars['type'];
357
358
		// Ignore subscription notes comments if it's not specifically requested.
359
		if ( 'subscription_note' !== $type ) {
360
			$clauses['where'] .= " AND comment_type != 'subscription_note'";
361
		}
362
363
		return $clauses;
364
	}
365
366
	/**
367
	 * Maybe schedule subscription payments.
368
	 */
369
	public function maybe_schedule_subscription_payments() {
370
		if ( wp_next_scheduled( 'pronamic_pay_update_subscription_payments' ) ) {
371
			return;
372
		}
373
374
		wp_schedule_event( time(), 'hourly', 'pronamic_pay_update_subscription_payments' );
375
	}
376
377
	/**
378
	 * Maybe create subscription for the specified payment.
379
	 *
380
	 * @param Payment $payment The new payment.
381
	 */
382
	public function maybe_create_subscription( $payment ) {
383
		// Check if there is already subscription attached to the payment.
384
		$subscription_id = $payment->get_subscription_id();
385
386
		if ( ! empty( $subscription_id ) ) {
387
			// Subscription already created.
388
			return;
389
		}
390
391
		// Check if there is a subscription object attached to the payment.
392
		$subscription_data = $payment->subscription;
393
394
		if ( empty( $subscription_data ) ) {
395
			return;
396
		}
397
398
		// New subscription.
399
		$subscription = new Subscription();
400
401
		$subscription->config_id       = $payment->config_id;
402
		$subscription->user_id         = $payment->user_id;
0 ignored issues
show
Bug introduced by
The property user_id does not seem to exist on Pronamic\WordPress\Pay\Subscriptions\Subscription.
Loading history...
Bug introduced by
The property user_id does not exist on Pronamic\WordPress\Pay\Payments\Payment. Did you mean user_ip?
Loading history...
403
		$subscription->title           = sprintf( __( 'Subscription for %s', 'pronamic_ideal' ), $payment->title );
0 ignored issues
show
Bug introduced by
The property title does not seem to exist on Pronamic\WordPress\Pay\Subscriptions\Subscription.
Loading history...
404
		$subscription->frequency       = $subscription_data->get_frequency();
405
		$subscription->interval        = $subscription_data->get_interval();
406
		$subscription->interval_period = $subscription_data->get_interval_period();
0 ignored issues
show
Documentation Bug introduced by
The property $interval_period was declared of type integer, but $subscription_data->get_interval_period() is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
407
		$subscription->key             = uniqid( 'subscr_' );
408
		$subscription->source          = $payment->source;
409
		$subscription->source_id       = $payment->source_id;
410
		$subscription->description     = $payment->description;
411
		$subscription->email           = $payment->email;
412
		$subscription->customer_name   = $payment->customer_name;
413
		$subscription->payment_method  = $payment->method;
414
		$subscription->status          = Statuses::OPEN;
415
		$subscription->set_amount( $subscription_data->get_amount() );
416
417
		// @todo
418
		// Calculate dates
419
		// @see https://github.com/pronamic/wp-pronamic-ideal/blob/4.7.0/classes/Pronamic/WP/Pay/Plugin.php#L883-L964
420
		$interval = $subscription->get_date_interval();
421
422
		$start_date  = clone $payment->date;
423
		$expiry_date = clone $start_date;
424
425
		$next_date = clone $start_date;
426
		$next_date->add( $interval );
427
428
		$end_date = null;
429
430
		if ( $subscription_data->frequency ) {
431
			// @see https://stackoverflow.com/a/10818981/6411283
432
			$period = new DatePeriod( $start_date, $interval, $subscription_data->frequency );
0 ignored issues
show
Bug introduced by
$subscription_data->frequency of type string is incompatible with the type DateTimeInterface expected by parameter $end of DatePeriod::__construct(). ( Ignorable by Annotation )

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

432
			$period = new DatePeriod( $start_date, $interval, /** @scrutinizer ignore-type */ $subscription_data->frequency );
Loading history...
433
434
			$dates = iterator_to_array( $period );
435
436
			$end_date = end( $dates );
437
		}
438
439
		$subscription->start_date   = $start_date;
440
		$subscription->end_date     = $end_date;
441
		$subscription->expiry_date  = $expiry_date;
442
		$subscription->next_payment = $next_date;
443
444
		// Create.
445
		$result = $this->plugin->subscriptions_data_store->create( $subscription );
446
447
		if ( $result ) {
448
			$payment->subscription    = $subscription;
449
			$payment->subscription_id = $subscription->get_id();
450
451
			$payment->recurring_type = Recurring::FIRST;
452
			$payment->start_date     = $start_date;
453
			$payment->end_date       = $next_date;
454
455
			$payment->save();
456
		}
457
	}
458
459
	/**
460
	 * Get expiring subscriptions.
461
	 *
462
	 * @see https://github.com/wp-premium/edd-software-licensing/blob/3.5.23/includes/license-renewals.php#L715-L746
463
	 * @see https://github.com/wp-premium/edd-software-licensing/blob/3.5.23/includes/license-renewals.php#L652-L712
464
	 *
465
	 * @param DateTime $start_date The start date of the period to check for expiring subscriptions.
466
	 * @param DateTime $end_date   The end date of the period to check for expiring subscriptions.
467
	 * @return array
468
	 */
469
	public function get_expiring_subscription_posts( DateTime $start_date, DateTime $end_date ) {
470
		$args = array(
471
			'post_type'   => 'pronamic_pay_subscr',
472
			'nopaging'    => true,
473
			'orderby'     => 'post_date',
474
			'order'       => 'ASC',
475
			'post_status' => array(
476
				'subscr_pending',
477
				'subscr_expired',
478
				'subscr_failed',
479
				'subscr_active',
480
			),
481
			'meta_query'  => array(
482
				array(
483
					'key'     => '_pronamic_subscription_expiry_date',
484
					'value'   => array(
485
						$start_date->format( DateTime::MYSQL ),
486
						$end_date->format( DateTime::MYSQL ),
487
					),
488
					'compare' => 'BETWEEN',
489
					'type'    => 'DATETIME',
490
				),
491
			),
492
		);
493
494
		$query = new WP_Query( $args );
495
496
		return $query->posts;
497
	}
498
499
	/**
500
	 * Payment status update.
501
	 *
502
	 * @param Payment $payment The status updated payment.
503
	 */
504
	public function payment_status_update( $payment ) {
505
		// Check if the payment is connected to a subscription.
506
		$subscription = $payment->get_subscription();
507
508
		if ( empty( $subscription ) ) {
509
			// Payment not connected to a subscription, nothing to do.
510
			return;
511
		}
512
513
		// Status.
514
		$status_before = $subscription->get_status();
515
		$status_update = $status_before;
516
517
		switch ( $payment->get_status() ) {
518
			case Statuses::OPEN:
519
				// @todo
520
				break;
521
			case Statuses::SUCCESS:
522
				$status_update = Statuses::ACTIVE;
523
524
				if ( isset( $subscription->expiry_date, $payment->end_date ) && $subscription->expiry_date < $payment->end_date ) {
525
					// @todo payment end date or subscription expiry date + 1 interval?
526
					$subscription->expiry_date = clone $payment->end_date;
527
				}
528
529
				break;
530
			case Statuses::FAILURE:
531
			case Statuses::CANCELLED:
532
			case Statuses::EXPIRED:
533
				$status_update = Statuses::CANCELLED;
534
535
				break;
536
		}
537
538
		// The status of canceled or completed subscriptions will not be changed automatically.
539
		if ( ! in_array( $status_before, array( Statuses::CANCELLED, Statuses::COMPLETED ), true ) ) {
540
			$subscription->set_status( $status_update );
541
		}
542
543
		// Update.
544
		$subscription->save();
545
	}
546
547
	/**
548
	 * Subscription status update.
549
	 *
550
	 * @param Subscription $subscription The status updated subscription.
551
	 * @param bool         $can_redirect Whether or not redirects should be performed.
552
	 * @param string|null  $old_status   Old meta status.
553
	 * @param string       $new_status   New meta status.
554
	 *
555
	 * @return void
556
	 */
557
	public function log_subscription_status_update( $subscription, $can_redirect, $old_status, $new_status ) {
558
		$note = sprintf(
559
			__( 'Subscription status changed from "%1$s" to "%2$s".', 'pronamic_ideal' ),
560
			esc_html( $this->plugin->subscriptions_data_store->get_meta_status_label( $old_status ) ),
561
			esc_html( $this->plugin->subscriptions_data_store->get_meta_status_label( $new_status ) )
562
		);
563
564
		if ( null === $old_status ) {
565
			$note = sprintf(
566
				__( 'Subscription created with status "%1$s".', 'pronamic_ideal' ),
567
				esc_html( $this->plugin->subscriptions_data_store->get_meta_status_label( $new_status ) )
568
			);
569
		}
570
571
		$subscription->add_note( $note );
572
	}
573
574
	/**
575
	 * Register privacy personal data exporter.
576
	 *
577
	 * @param $exporters
578
	 *
579
	 * @return array
580
	 */
581
	public function register_privacy_exporter( $exporters ) {
582
		if ( ! is_array( $exporters ) ) {
583
			return $exporters;
584
		}
585
586
		$exporters['pronamic-pay-payments'] = array(
587
			'exporter_friendly_name' => __( 'Pronamic Pay', 'pronamic_ideal' ),
588
			'callback'               => array( $this, 'privacy_export' ),
589
		);
590
591
		return $exporters;
592
	}
593
594
	/**
595
	 * Privacy personal data exporter.
596
	 *
597
	 * @param string $email_address Email address.
598
	 * @param int    $page          Page.
599
	 *
600
	 * @return array
601
	 */
602
	public function privacy_export( $email_address, $page = 1 ) {
603
		$items = array();
604
605
		$meta_key_email = pronamic_pay_plugin()->subscriptions_data_store->meta_key_prefix . 'email';
606
607
		// Get subscriptions.
608
		$subscriptions = get_pronamic_subscriptions_by_meta( $meta_key_email, $email_address );
609
610
		foreach ( $subscriptions as $subscription ) {
611
			$data = array();
612
613
			// Get subscription meta.
614
			$subscription_meta = get_post_meta( $subscription->get_id() );
615
616
			foreach ( $subscription_meta as $meta_key => $meta_value ) {
617
				if ( '_pronamic_' !== substr( $meta_key, 0, 10 ) ) {
618
					continue;
619
				}
620
621
				// Format value.
622
				if ( 1 === count( $meta_value ) ) {
623
					$meta_value = array_shift( $meta_value );
624
				} else {
625
					$meta_value = wp_json_encode( $meta_value );
626
				}
627
628
				// Add meta to export data.
629
				$data[] = array(
630
					'name'  => $meta_key,
631
					'value' => $meta_value,
632
				);
633
			}
634
635
			// Add item to export data.
636
			if ( ! empty( $data ) ) {
637
				$items[] = array(
638
					'group_id'    => 'pronamic-subscriptions',
639
					'group_label' => __( 'Subscriptions', 'pronamic_ideal' ),
640
					'item_id'     => 'pronamic-subscription-' . $subscription->get_id(),
641
					'data'        => $data,
642
				);
643
			}
644
		}
645
646
		$done = true;
647
648
		// Return export data.
649
		return array(
650
			'data' => $items,
651
			'done' => $done,
652
		);
653
	}
654
655
	/**
656
	 * Send renewal notices.
657
	 *
658
	 * @see https://github.com/wp-premium/edd-software-licensing/blob/3.5.23/includes/license-renewals.php#L652-L712
659
	 * @see https://github.com/wp-premium/edd-software-licensing/blob/3.5.23/includes/license-renewals.php#L715-L746
660
	 * @see https://github.com/wp-premium/edd-software-licensing/blob/3.5.23/includes/classes/class-sl-emails.php#L41-L126
661
	 */
662
	public function send_subscription_renewal_notices() {
663
		$interval = new DateInterval( 'P1W' ); // 1 week
664
665
		$start_date = new DateTime( 'midnight', new DateTimeZone( 'UTC' ) );
666
667
		$end_date = clone $start_date;
668
		$end_date->add( $interval );
669
670
		$expiring_subscription_posts = $this->get_expiring_subscription_posts( $start_date, $end_date );
671
672
		foreach ( $expiring_subscription_posts as $post ) {
673
			$subscription = new Subscription( $post->ID );
674
675
			$expiry_date = $subscription->get_expiry_date();
676
677
			$sent_date_string = get_post_meta( $post->ID, '_pronamic_subscription_renewal_sent_1week', true );
678
679
			if ( $sent_date_string ) {
680
				$first_date = clone $expiry_date;
681
				$first_date->sub( $subscription->get_date_interval() );
682
683
				$sent_date = new DateTime( $sent_date_string, new DateTimeZone( 'UTC' ) );
684
685
				if ( $sent_date >= $first_date || $expiry_date < $subscription->get_next_payment_date() ) {
686
					// Prevent renewal notices from being sent more than once.
687
					continue;
688
				}
689
690
				delete_post_meta( $post->ID, '_pronamic_subscription_renewal_sent_1week' );
691
			}
692
693
			// Add renewal notice payment note.
694
			$note = sprintf(
695
				__( 'Subscription renewal due on %s.', 'pronamic_ideal' ),
696
				$expiry_date->format_i18n()
697
			);
698
699
			$subscription->add_note( $note );
700
701
			// Send renewal notice.
702
			do_action( 'pronamic_subscription_renewal_notice_' . $subscription->get_source(), $subscription );
703
704
			// Update renewal notice sent date meta.
705
			$renewal_sent_date = clone $start_date;
706
707
			$renewal_sent_date->setTime( $expiry_date->format( 'H' ), $expiry_date->format( 'i' ), $expiry_date->format( 's' ) );
0 ignored issues
show
Bug introduced by
$expiry_date->format('i') of type string is incompatible with the type integer expected by parameter $minute of DateTime::setTime(). ( Ignorable by Annotation )

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

707
			$renewal_sent_date->setTime( $expiry_date->format( 'H' ), /** @scrutinizer ignore-type */ $expiry_date->format( 'i' ), $expiry_date->format( 's' ) );
Loading history...
Bug introduced by
$expiry_date->format('s') of type string is incompatible with the type integer expected by parameter $second of DateTime::setTime(). ( Ignorable by Annotation )

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

707
			$renewal_sent_date->setTime( $expiry_date->format( 'H' ), $expiry_date->format( 'i' ), /** @scrutinizer ignore-type */ $expiry_date->format( 's' ) );
Loading history...
Bug introduced by
$expiry_date->format('H') of type string is incompatible with the type integer expected by parameter $hour of DateTime::setTime(). ( Ignorable by Annotation )

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

707
			$renewal_sent_date->setTime( /** @scrutinizer ignore-type */ $expiry_date->format( 'H' ), $expiry_date->format( 'i' ), $expiry_date->format( 's' ) );
Loading history...
708
709
			update_post_meta( $post->ID, '_pronamic_subscription_renewal_sent_1week', $renewal_sent_date->format( DateTime::MYSQL ) );
710
		}
711
	}
712
713
	/**
714
	 * Update subscription payments.
715
	 *
716
	 * @param bool $cli_test Whether or not this a CLI test.
717
	 */
718
	public function update_subscription_payments( $cli_test = false ) {
719
		$this->send_subscription_renewal_notices();
720
721
		$args = array(
722
			'post_type'   => 'pronamic_pay_subscr',
723
			'nopaging'    => true,
724
			'orderby'     => 'post_date',
725
			'order'       => 'ASC',
726
			'post_status' => array(
727
				'subscr_pending',
728
				'subscr_expired',
729
				'subscr_failed',
730
				'subscr_active',
731
			),
732
			'meta_query'  => array(
733
				array(
734
					'key'     => '_pronamic_subscription_source',
735
					'compare' => 'NOT IN',
736
					'value'   => array(
737
						// Don't create payments for sources which schedule payments.
738
						'woocommerce',
739
					),
740
				),
741
			),
742
		);
743
744
		if ( ! $cli_test ) {
745
			$args['meta_query'][] = array(
746
				'key'     => '_pronamic_subscription_next_payment',
747
				'compare' => '<=',
748
				'value'   => current_time( 'mysql', true ),
749
				'type'    => 'DATETIME',
750
			);
751
		}
752
753
		$query = new WP_Query( $args );
754
755
		foreach ( $query->posts as $post ) {
756
			if ( $cli_test ) {
757
				WP_CLI::log( sprintf( 'Processing post `%d` - "%s"…', $post->ID, get_the_title( $post ) ) );
758
			}
759
760
			$subscription = new Subscription( $post->ID );
761
762
			$gateway = Plugin::get_gateway( $subscription->config_id );
763
764
			// Start payment.
765
			$payment = $this->start_recurring( $subscription, $gateway );
766
767
			if ( $payment ) {
768
				// Update payment.
769
				Plugin::update_payment( $payment, false );
770
			}
771
772
			// Expire manual renewal subscriptions.
773
			if ( ! $gateway->supports( 'recurring' ) ) {
774
				$now = new DateTime();
775
776
				if ( Statuses::COMPLETED !== $subscription->status && isset( $subscription->expiry_date ) && $subscription->expiry_date <= $now ) {
777
					$subscription->status = Statuses::EXPIRED;
778
779
					$subscription->save();
780
781
					// Delete next payment date so it won't get used as start date
782
					// of the new payment period when manually renewing and to keep
783
					// the subscription out of updating subscription payments (this method).
784
					$subscription->set_meta( 'next_payment', null );
785
				}
786
			}
787
		}
788
	}
789
790
	/**
791
	 * CLI subscriptions test.
792
	 */
793
	public function cli_subscriptions_test() {
794
		$cli_test = true;
795
796
		$this->update_subscription_payments( $cli_test );
797
798
		WP_CLI::success( 'Pronamic Pay subscriptions test.' );
799
	}
800
}
801