Failed Conditions
Push — develop ( 2dbcf1...bdd916 )
by Remco
06:44
created

src/Extension.php (8 issues)

1
<?php
2
/**
3
 * Extension
4
 *
5
 * @author    Pronamic <[email protected]>
6
 * @copyright 2005-2021 Pronamic
7
 * @license   GPL-3.0-or-later
8
 * @package   Pronamic\WordPress\Pay\Extensions\MemberPress
9
 */
10
11
namespace Pronamic\WordPress\Pay\Extensions\MemberPress;
12
13
use MeprDb;
14
use MeprOptions;
15
use MeprProduct;
16
use MeprSubscription;
17
use MeprTransaction;
18
use MeprUtils;
19
use Pronamic\WordPress\Pay\AbstractPluginIntegration;
20
use Pronamic\WordPress\Pay\Payments\PaymentStatus;
21
use Pronamic\WordPress\Pay\Extensions\MemberPress\Gateways\Gateway;
22
use Pronamic\WordPress\Pay\Payments\Payment;
23
use Pronamic\WordPress\Pay\Subscriptions\Subscription;
24
use Pronamic\WordPress\Pay\Subscriptions\SubscriptionStatus;
25
26
/**
27
 * WordPress pay MemberPress extension
28
 *
29
 * @author  Remco Tolsma
30
 * @version 2.2.3
31
 * @since   1.0.0
32
 */
33
class Extension extends AbstractPluginIntegration {
34
	/**
35
	 * The slug of this addon
36
	 *
37
	 * @var string
38
	 */
39
	const SLUG = 'memberpress';
40
41
	/**
42
	 * Construct MemberPress plugin integration.
43
	 */
44
	public function __construct() {
45
		parent::__construct(
46
			array(
47
				'name' => __( 'MemberPress', 'pronamic_ideal' ),
48
			)
49
		);
50
51
		// Dependencies.
52
		$dependencies = $this->get_dependencies();
53
54
		$dependencies->add( new MemberPressDependency() );
55
	}
56
57
	/**
58
	 * Setup.
59
	 */
60
	public function setup() {
61
		\add_filter( 'pronamic_subscription_source_description_' . self::SLUG, array( $this, 'subscription_source_description' ), 10, 2 );
62
		\add_filter( 'pronamic_payment_source_description_' . self::SLUG, array( $this, 'source_description' ), 10, 2 );
63
64
		// Check if dependencies are met and integration is active.
65
		if ( ! $this->is_active() ) {
66
			return;
67
		}
68
69
		// @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/lib/MeprGatewayFactory.php#L48-50
70
		\add_filter( 'mepr-gateway-paths', array( $this, 'gateway_paths' ) );
71
72
		\add_filter( 'pronamic_payment_redirect_url_' . self::SLUG, array( $this, 'redirect_url' ), 10, 2 );
73
		\add_action( 'pronamic_payment_status_update_' . self::SLUG, array( $this, 'status_update' ), 10, 1 );
74
75
		\add_action( 'pronamic_pay_new_payment', array( $this, 'maybe_create_memberpress_transaction' ), 10, 1 );
76
77
		\add_filter( 'pronamic_subscription_source_text_' . self::SLUG, array( $this, 'subscription_source_text' ), 10, 2 );
78
		\add_filter( 'pronamic_subscription_source_url_' . self::SLUG, array( $this, 'subscription_source_url' ), 10, 2 );
79
		\add_filter( 'pronamic_payment_source_text_' . self::SLUG, array( $this, 'source_text' ), 10, 2 );
80
		\add_filter( 'pronamic_payment_source_url_' . self::SLUG, array( $this, 'source_url' ), 10, 2 );
81
82
		\add_action( 'mepr_subscription_pre_delete', array( $this, 'subscription_pre_delete' ), 10, 1 );
83
84
		\add_action( 'mepr_subscription_transition_status', array( $this, 'memberpress_subscription_transition_status' ), 10, 3 );
85
86
		// MemberPress subscription email parameters.
87
		\add_filter( 'mepr_subscription_email_params', array( $this, 'subscription_email_params' ), 10, 2 );
88
		\add_filter( 'mepr_transaction_email_params', array( $this, 'transaction_email_params' ), 10, 2 );
89
90
		// Hide MemberPress columns for payments and subscriptions.
91
		\add_action( 'registered_post_type', array( $this, 'post_type_columns_hide' ), 15, 1 );
92
93
		if ( \is_admin() ) {
94
			$admin_subscriptions = new Admin\AdminSubscriptions();
95
			$admin_transactions  = new Admin\AdminTransactions();
96
97
			$admin_subscriptions->setup();
98
			$admin_transactions->setup();
99
		}
100
	}
101
102
	/**
103
	 * Gateway paths.
104
	 *
105
	 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/lib/MeprGatewayFactory.php#L49
106
	 * @param string[] $paths Array with gateway paths.
107
	 * @return string[]
108
	 */
109
	public function gateway_paths( $paths ) {
110
		$paths[] = dirname( __FILE__ ) . '/../gateways/';
111
112
		return $paths;
113
	}
114
115
	/**
116
	 * Hide MemberPress columns for payments and subscriptions.
117
	 *
118
	 * @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/controllers/MeprAppCtrl.php#L129-146
119
	 *
120
	 * @param string $post_type Registered post type.
121
	 *
122
	 * @return void
123
	 */
124
	public function post_type_columns_hide( $post_type ) {
125
		if ( ! in_array( $post_type, array( 'pronamic_payment', 'pronamic_pay_subscr' ), true ) ) {
126
			return;
127
		}
128
129
		remove_filter( 'manage_edit-' . $post_type . '_columns', 'MeprAppCtrl::columns' );
130
	}
131
132
	/**
133
	 * Payment redirect URL filter.
134
	 *
135
	 * @since 1.0.1
136
	 *
137
	 * @param string  $url     Payment redirect URL.
138
	 * @param Payment $payment Payment to redirect for.
139
	 *
140
	 * @return string
141
	 */
142
	public function redirect_url( $url, Payment $payment ) {
143
		global $transaction;
144
145
		$transaction_id = $payment->get_source_id();
146
147
		$transaction = new MeprTransaction( $transaction_id );
148
149
		switch ( $payment->get_status() ) {
150
			case PaymentStatus::CANCELLED:
151
			case PaymentStatus::EXPIRED:
152
			case PaymentStatus::FAILURE:
153
				$product = $transaction->product();
154
155
				$url = add_query_arg(
156
					array(
157
						'action'   => 'payment_form',
158
						'txn'      => $transaction->trans_num,
159
						'errors'   => array(
160
							__( 'Payment failed. Please try again.', 'pronamic_ideal' ),
161
						),
162
						'_wpnonce' => wp_create_nonce( 'mepr_payment_form' ),
163
					),
164
					$product->url()
165
				);
166
167
				break;
168
			case PaymentStatus::SUCCESS:
169
				// @link https://gitlab.com/pronamic/memberpress/blob/1.2.4/app/models/MeprOptions.php#L768-782
170
				$mepr_options = MeprOptions::fetch();
171
172
				$product         = new MeprProduct( $transaction->product_id );
173
				$sanitized_title = sanitize_title( $product->post_title );
174
175
				$args = array(
176
					'membership_id' => $product->ID,
177
					'membership'    => $sanitized_title,
178
					'trans_num'     => $transaction->trans_num,
179
				);
180
181
				$url = $mepr_options->thankyou_page_url( http_build_query( $args ) );
182
183
				break;
184
			case PaymentStatus::OPEN:
185
			default:
186
				break;
187
		}
188
189
		return $url;
190
	}
191
192
	/**
193
	 * Maybe create create MemberPress transaction for the Pronamic payment.
194
	 * 
195
	 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/models/MeprSubscription.php
196
	 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/gateways/MeprStripeGateway.php#L587-L714
197
	 * @param Payment $payment Payment.
198
	 * @return void
199
	 */
200
	public function maybe_create_memberpress_transaction( Payment $payment ) {
201
		if ( 'memberpress_subscription' !== $payment->get_source() ) {
202
			return;
203
		}
204
205
		$memberpress_subscription_id = $payment->get_source_id();
206
207
		$memberpress_subscription = MemberPress::get_subscription_by_id( $memberpress_subscription_id );
208
209
		if ( null === $memberpress_subscription ) {
210
			throw new \Exception(
211
				\sprintf(
212
					'Could not find MemberPress subscription with ID: %s.',
213
					$memberpress_subscription_id
214
				)
215
			);
216
		}
217
218
		/**
219
		 * If the payment method is changed we have to update the MemberPress
220
		 * subscription.
221
		 * 
222
		 * @link https://github.com/wp-pay-extensions/memberpress/commit/3631bcb24f376fb637c1317e15f540cb1f9136f4#diff-6f62438f6bf291e85f644dbdbb14b2a71a9a7ed205b01ce44290ed85abe2aa07L259-L290
223
		 */
224
		$memberpress_gateways = MeprOptions::fetch()->payment_methods();
225
226
		foreach ( $memberpress_gateways as $memberpress_gateway ) {
227
			if ( ! $memberpress_gateway instanceof Gateway ) {
228
				continue;
229
			}
230
231
			if ( $memberpress_gateway->get_payment_method() === $payment->get_method() ) {
232
				$memberpress_subscription->gateway = $memberpress_gateway->id;
233
			}
234
		}
235
236
		/**
237
		 * Payment method.
238
		 * 
239
		 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/models/MeprTransaction.php#L634-L637
240
		 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/models/MeprOptions.php#L798-L811
241
		 */
242
		$memberpress_gateway = $memberpress_subscription->payment_method();
243
244
		if ( ! $memberpress_gateway instanceof Gateway ) {
245
			return;
246
		}
247
248
		/**
249
		 * At this point we should call `MeprBaseRealGateway->record_subscription_payment`.
250
		 * 
251
		 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/gateways/MeprStripeGateway.php#L587-L714
252
		 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/gateways/MeprAuthorizeGateway.php#L205-L255
253
		 */
254
		$memberpress_gateway->record_subscription_payment();
255
256
		$memberpress_transaction = new MeprTransaction();
257
258
		$memberpress_transaction->user_id         = $memberpress_subscription->user_id;
259
		$memberpress_transaction->product_id      = $memberpress_subscription->product_id;
0 ignored issues
show
Bug Best Practice introduced by
The property product_id does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
260
		$memberpress_transaction->txn_type        = MeprTransaction::$payment_str;
0 ignored issues
show
Bug Best Practice introduced by
The property txn_type does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
261
		$memberpress_transaction->status          = MeprTransaction::$pending_str;
0 ignored issues
show
Bug Best Practice introduced by
The property status does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
262
		$memberpress_transaction->coupon_id       = $memberpress_subscription->coupon_id;
0 ignored issues
show
Bug Best Practice introduced by
The property coupon_id does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
263
		$memberpress_transaction->trans_num       = $payment->get_transaction_id();
0 ignored issues
show
Bug Best Practice introduced by
The property trans_num does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
264
		$memberpress_transaction->subscription_id = $memberpress_subscription->id;
0 ignored issues
show
Bug Best Practice introduced by
The property subscription_id does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
265
		$memberpress_transaction->gateway         = $memberpress_gateway->id;
0 ignored issues
show
Bug Best Practice introduced by
The property gateway does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
266
267
		/**
268
		 * Gross.
269
		 * 
270
		 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/models/MeprTransaction.php#L1013-L1021
271
		 */
272
		$memberpress_transaction->set_gross( $payment->get_total_amount()->get_value() );
273
274
		$memberpress_transaction->store();
275
276
		$memberpress_subscription->store();
277
278
		MeprUtils::send_transaction_receipt_notices( $memberpress_transaction );
279
	}
280
281
	/**
282
	 * Update lead status of the specified payment.
283
	 *
284
	 * @param Payment $payment The payment whose status is updated.
285
	 * @return void
286
	 */
287
	public function status_update( Payment $payment ) {
288
		$payment_source_id = $payment->get_source_id();
289
290
		$memberpress_transaction = MemberPress::get_transaction_by_id( $payment_source_id );
291
292
		/**
293
		 * If we can't find a MemberPress transaction by the payment source ID
294
		 * we can't update the MemberPress transaction, bail out early.
295
		 */
296
		if ( null === $memberpress_transaction ) {
297
			return;
298
		}
299
300
		/**
301
		 * We don't update MemberPress transactions that already have the
302
		 * status 'failed' or 'complete'.
303
		 */
304
		if ( MemberPress::transaction_has_status(
305
			$memberpress_transaction,
306
			array(
307
				MeprTransaction::$failed_str,
308
				MeprTransaction::$complete_str,
309
			)
310
		) ) {
311
			return;
312
		}
313
314
		/**
315
		 * Payment method.
316
		 * 
317
		 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/models/MeprTransaction.php#L634-L637
318
		 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/models/MeprOptions.php#L798-L811
319
		 */
320
		$memberpress_gateway = $memberpress_transaction->payment_method();
321
322
		if ( ! $memberpress_gateway instanceof Gateway ) {
323
			return;
324
		} 
325
326
		$memberpress_gateway->set_record_data( $payment, $memberpress_transaction );
327
328
		switch ( $payment->get_status() ) {
329
			case PaymentStatus::FAILURE:
330
			case PaymentStatus::CANCELLED:
331
			case PaymentStatus::EXPIRED:
332
				$memberpress_gateway->record_payment_failure();
333
334
				break;
335
			case PaymentStatus::SUCCESS:
336
				$memberpress_gateway->record_payment();
337
338
				break;
339
			case PaymentStatus::OPEN:
340
			default:
341
				break;
342
		}
343
	}
344
345
	/**
346
	 * Subscription deleted.
347
	 *
348
	 * @param int $subscription_id MemberPress subscription id.
349
	 * @return void
350
	 */
351
	public function subscription_pre_delete( $subscription_id ) {
352
		$subscription = get_pronamic_subscription_by_meta( '_pronamic_subscription_source_id', $subscription_id );
353
354
		if ( ! $subscription ) {
355
			return;
356
		}
357
358
		// Add note.
359
		$note = sprintf(
360
			/* translators: %s: MemberPress */
361
			__( '%s subscription deleted.', 'pronamic_ideal' ),
362
			__( 'MemberPress', 'pronamic_ideal' )
363
		);
364
365
		$subscription->add_note( $note );
366
367
		// The status of canceled or completed subscriptions will not be changed automatically.
368
		if ( ! in_array( $subscription->get_status(), array( SubscriptionStatus::CANCELLED, SubscriptionStatus::COMPLETED ), true ) ) {
369
			$subscription->set_status( SubscriptionStatus::CANCELLED );
370
371
			$subscription->save();
372
		}
373
	}
374
375
	/**
376
	 * Subscription email parameters.
377
	 *
378
	 * @param array<string, string> $params                   Email parameters.
379
	 * @param MeprSubscription      $memberpress_subscription MemberPress subscription.
380
	 * @return array<string, string>
381
	 */
382
	public function subscription_email_params( $params, MeprSubscription $memberpress_subscription ) {
383
		$subscriptions = \get_pronamic_subscriptions_by_source( 'memberpress', $memberpress_subscription->id );
384
385
		if ( empty( $subscriptions ) ) {
386
			return $params;
387
		}
388
389
		$subscription = reset( $subscriptions );
390
391
		// Add parameters.
392
		$next_payment_date = $subscription->get_next_payment_date();
393
394
		return \array_merge(
395
			$params,
396
			array(
397
				'pronamic_subscription_id'           => (string) $subscription->get_id(),
398
				'pronamic_subscription_cancel_url'   => $subscription->get_cancel_url(),
399
				'pronamic_subscription_renewal_url'  => $subscription->get_renewal_url(),
400
				'pronamic_subscription_renewal_date' => null === $next_payment_date ? '' : \date_i18n( \get_option( 'date_format' ), $next_payment_date->getTimestamp() ),
0 ignored issues
show
It seems like get_option('date_format') can also be of type false; however, parameter $format of date_i18n() does only seem to accept string, 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

400
				'pronamic_subscription_renewal_date' => null === $next_payment_date ? '' : \date_i18n( /** @scrutinizer ignore-type */ \get_option( 'date_format' ), $next_payment_date->getTimestamp() ),
Loading history...
401
			)
402
		);
403
	}
404
405
	/**
406
	 * Transaction email parameters.
407
	 *
408
	 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/helpers/MeprTransactionsHelper.php#L233
409
	 * @param array<string, string> $params      Parameters.
410
	 * @param MeprTransaction       $transaction MemberPress transaction.
411
	 * @return array<string, string>
412
	 */
413
	public function transaction_email_params( $params, MeprTransaction $transaction ) {
414
		$payments = \get_pronamic_payments_by_source( 'memberpress', $transaction->id );
415
416
		if ( null === $payments ) {
417
			return $params;
418
		}
419
420
		$payment = \reset( $payments );
421
422
		if ( false === $payment ) {
423
			return $params;
424
		}
425
426
		// Get subscription.
427
		$periods = $payment->get_periods();
428
429
		if ( null === $periods ) {
430
			return $params;
431
		}
432
433
		$period = \reset( $periods );
434
435
		if ( false === $period ) {
436
			return $params;
437
		}
438
439
		$subscription = $period->get_phase()->get_subscription();
440
441
		// Add parameters.
442
		$memberpress_subscription = new MeprSubscription( $subscription->get_source_id() );
443
444
		return $this->subscription_email_params( $params, $memberpress_subscription );
445
	}
446
447
	/**
448
	 * Source text.
449
	 *
450
	 * @param string  $text    Source text.
451
	 * @param Payment $payment Payment to create the source text for.
452
	 *
453
	 * @return string
454
	 */
455
	public function source_text( $text, Payment $payment ) {
456
		$text = __( 'MemberPress', 'pronamic_ideal' ) . '<br />';
457
458
		$text .= sprintf(
459
			'<a href="%s">%s</a>',
460
			add_query_arg(
461
				array(
462
					'page'   => 'memberpress-trans',
463
					'action' => 'edit',
464
					'id'     => $payment->source_id,
465
				),
466
				admin_url( 'admin.php' )
467
			),
468
			/* translators: %s: payment source id */
469
			sprintf( __( 'Transaction %s', 'pronamic_ideal' ), $payment->source_id )
470
		);
471
472
		return $text;
473
	}
474
475
	/**
476
	 * Subscription source text.
477
	 *
478
	 * @param string       $text         Source text.
479
	 * @param Subscription $subscription Subscription to create the source text for.
480
	 *
481
	 * @return string
482
	 */
483
	public function subscription_source_text( $text, Subscription $subscription ) {
484
		$text = __( 'MemberPress', 'pronamic_ideal' ) . '<br />';
485
486
		$text .= sprintf(
487
			'<a href="%s">%s</a>',
488
			add_query_arg(
489
				array(
490
					'page'         => 'memberpress-subscriptions',
491
					'subscription' => $subscription->source_id,
492
				),
493
				admin_url( 'admin.php' )
494
			),
495
			/* translators: %s: payment source id */
496
			sprintf( __( 'Subscription %s', 'pronamic_ideal' ), $subscription->source_id )
497
		);
498
499
		return $text;
500
	}
501
502
	/**
503
	 * Source description.
504
	 *
505
	 * @param string  $description Description.
506
	 * @param Payment $payment     Payment to create the description for.
507
	 *
508
	 * @return string
509
	 */
510
	public function source_description( $description, Payment $payment ) {
511
		return __( 'MemberPress Transaction', 'pronamic_ideal' );
512
	}
513
514
	/**
515
	 * Subscription source description.
516
	 *
517
	 * @param string       $description  Description.
518
	 * @param Subscription $subscription Subscription to create the description for.
519
	 *
520
	 * @return string
521
	 */
522
	public function subscription_source_description( $description, Subscription $subscription ) {
523
		return __( 'MemberPress Subscription', 'pronamic_ideal' );
524
	}
525
526
	/**
527
	 * Source URL.
528
	 *
529
	 * @param string  $url     URL.
530
	 * @param Payment $payment The payment to create the source URL for.
531
	 *
532
	 * @return string
533
	 */
534
	public function source_url( $url, Payment $payment ) {
535
		$url = add_query_arg(
536
			array(
537
				'page'   => 'memberpress-trans',
538
				'action' => 'edit',
539
				'id'     => $payment->source_id,
540
			),
541
			admin_url( 'admin.php' )
542
		);
543
544
		return $url;
545
	}
546
547
	/**
548
	 * Subscription source URL.
549
	 *
550
	 * @param string       $url          URL.
551
	 * @param Subscription $subscription Subscription.
552
	 *
553
	 * @return string
554
	 */
555
	public function subscription_source_url( $url, Subscription $subscription ) {
556
		$url = add_query_arg(
557
			array(
558
				'page'         => 'memberpress-subscriptions',
559
				'subscription' => $subscription->source_id,
560
			),
561
			admin_url( 'admin.php' )
562
		);
563
564
		return $url;
565
	}
566
567
	/**
568
	 * MemberPress update subscription.
569
	 *
570
	 * @link https://github.com/wp-premium/memberpress-basic/blob/1.3.18/app/controllers/MeprSubscriptionsCtrl.php#L92-L111
571
	 * @link https://github.com/wp-premium/memberpress-basic/blob/1.3.18/app/models/MeprSubscription.php#L100-L123
572
	 * @link https://github.com/wp-premium/memberpress-basic/blob/1.3.18/app/models/MeprSubscription.php#L112
573
	 * @link https://github.com/wp-premium/memberpress/blob/1.9.21/app/models/MeprSubscription.php#L122
574
	 * @param string           $status_old               Old status identifier.
575
	 * @param string           $status_new               New status identifier.
576
	 * @param MeprSubscription $memberpress_subscription MemberPress subscription object.
577
	 * @return void
578
	 */
579
	public function memberpress_subscription_transition_status( $status_old, $status_new, $memberpress_subscription ) {
580
		$subscription = get_pronamic_subscription_by_meta( '_pronamic_subscription_source_id', $memberpress_subscription->id );
581
582
		if ( empty( $subscription ) ) {
583
			return;
584
		}
585
586
		$status = SubscriptionStatuses::transform( $status_new );
587
588
		if ( null === $status ) {
589
			return;
590
		}
591
592
		$subscription->set_status( $status );
593
594
		$subscription->save();
595
	}
596
}
597