PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.34
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.34
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / stripe / controllers / FrmTransLiteActionsController.php

FrmTransLiteActionsController.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.34, at stripe/controllers/FrmTransLiteActionsController.php

701 lines 18.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if ( ! defined( 'ABSPATH' ) ) {
3 die( 'You are not allowed to call this page directly.' );
4 }
5
6 class FrmTransLiteActionsController {
7
8 /**
9 * Track the entry IDs we're destroying so we don't attempt to delete an entry more than once.
10 * Set in self::destroy_entry_later.
11 *
12 * @var array
13 */
14 private static $entry_ids_to_destroy_later = array();
15
16 /**
17 * Register payment action type.
18 *
19 * @param array $actions
20 *
21 * @return array
22 */
23 public static function register_actions( $actions ) {
24 $actions['payment'] = 'FrmTransLiteAction';
25 return $actions;
26 }
27
28 /**
29 * Include scripts for handling payments at an administrative level.
30 * This includes handling the after payment settings for Stripe actions.
31 * It also handles refunds and canceling subscriptions.
32 *
33 * @return void
34 */
35 public static function actions_js() {
36 wp_enqueue_script(
37 'frmtrans_admin',
38 FrmTransLiteAppHelper::plugin_url() . '/js/frmtrans_admin.js',
39 array( 'jquery', 'wp-hooks' ),
40 FrmAppHelper::plugin_version()
41 );
42 wp_localize_script(
43 'frmtrans_admin',
44 'frm_trans_vars',
45 array(
46 'nonce' => wp_create_nonce( 'frm_trans_ajax' ),
47 )
48 );
49 }
50
51 /**
52 * Add event types for actions so an email can trigger on a successful payment.
53 *
54 * @param array $triggers
55 *
56 * @return array
57 */
58 public static function add_payment_trigger( $triggers ) {
59 $triggers['payment-success'] = __( 'Successful Payment', 'formidable' );
60 $triggers['payment-failed'] = __( 'Failed Payment', 'formidable' );
61 $triggers['payment-refunded'] = __( 'Refunded Payment', 'formidable' );
62 $triggers['payment-processing'] = __( 'Processing Payment', 'formidable' );
63 $triggers['payment-future-cancel'] = __( 'Canceled Subscription', 'formidable' );
64 $triggers['payment-canceled'] = __( 'Subscription Canceled and Expired', 'formidable' );
65 return $triggers;
66 }
67
68 /**
69 * @param array $options
70 *
71 * @return array
72 */
73 public static function add_trigger_to_action( $options ) {
74 $options['event'][] = 'payment-success';
75 $options['event'][] = 'payment-failed';
76 $options['event'][] = 'payment-processing';
77 $options['event'][] = 'payment-future-cancel';
78 $options['event'][] = 'payment-canceled';
79 $options['event'][] = 'payment-refunded';
80 return $options;
81 }
82
83 /**
84 * @param WP_Post $action
85 * @param stdClass $entry
86 * @param mixed $form
87 *
88 * @return void
89 */
90 public static function trigger_action( $action, $entry, $form ) {
91 self::prepare_description( $action, compact( 'entry', 'form' ) );
92
93 $gateway = self::get_gateway_for_action( $action );
94
95 if ( ! $gateway ) {
96 return;
97 }
98
99 $class_name = FrmTransLiteAppHelper::get_setting_for_gateway( $gateway, 'class' );
100
101 if ( ! $class_name ) {
102 return;
103 }
104
105 $class_name = 'Frm' . $class_name . 'ActionsController';
106 $response = $class_name::trigger_gateway( $action, $entry, $form );
107
108 if ( ! $response['success'] && $response['show_errors'] ) {
109 // The payment failed
110 self::show_failed_message( compact( 'action', 'entry', 'form', 'response' ) );
111 }
112 }
113
114 /**
115 * @param WP_Post $action
116 *
117 * @return array|string
118 */
119 private static function get_gateway_for_action( $action ) {
120 return $action->post_content['gateway'] ?? 'stripe';
121 }
122
123 /**
124 * @since 6.10
125 *
126 * @param array $args
127 *
128 * @return void
129 */
130 private static function show_failed_message( $args ) {
131 global $frm_vars;
132 $frm_vars['frm_trans'] = array(
133 'pay_entry' => $args['entry'],
134 'error' => $args['response']['error'] ?? '',
135 );
136
137 add_filter( 'frm_success_filter', 'FrmTransLiteActionsController::force_message_after_create' );
138 add_filter( 'frm_pre_display_form', 'FrmTransLiteActionsController::include_form_with_success' );
139 add_filter( 'frm_main_feedback', 'FrmTransLiteActionsController::replace_success_message', 5 );
140 add_filter( 'frm_setup_new_fields_vars', 'FrmTransLiteActionsController::fill_entry_from_previous', 20, 2 );
141 }
142
143 /**
144 * @since 6.10
145 *
146 * @param stdClass $form
147 *
148 * @return stdClass
149 */
150 public static function include_form_with_success( $form ) {
151 $form->options['show_form'] = 1;
152 return $form;
153 }
154
155 /**
156 * @return string
157 */
158 public static function replace_success_message() {
159 global $frm_vars;
160 $message = $frm_vars['frm_trans']['error'] ?? '';
161
162 if ( ! $message ) {
163 $message = __( 'There was an error processing your payment.', 'formidable' );
164 }
165
166 return '<div class="frm_error_style">' . $message . '</div>';
167 }
168
169 /**
170 * @param WP_Post $action
171 * @param stdClass $entry
172 * @param mixed $form
173 *
174 * @return array
175 */
176 public static function trigger_gateway( $action, $entry, $form ) {
177 // This function must be overridden in a subclass.
178 return array(
179 'success' => false,
180 'run_triggers' => false,
181 'show_errors' => true,
182 );
183 }
184
185 /**
186 * @return string
187 */
188 public static function force_message_after_create() {
189 return 'message';
190 }
191
192 /**
193 * @since 6.5, introduced in v1.12 of the Payments submodule.
194 *
195 * @param object $sub
196 *
197 * @return void
198 */
199 public static function trigger_subscription_status_change( $sub ) {
200 $frm_payment = new FrmTransLitePayment();
201 $payment = $frm_payment->get_one_by( $sub->id, 'sub_id' );
202
203 if ( $payment && $payment->action_id ) {
204 self::trigger_payment_status_change(
205 array(
206 'status' => $sub->status,
207 'payment' => $payment,
208 )
209 );
210 }
211 }
212
213 /**
214 * @param array $atts
215 *
216 * @return void
217 */
218 public static function trigger_payment_status_change( $atts ) {
219 $entry_id = isset( $atts['entry'] ) ? $atts['entry']->id : $atts['payment']->item_id;
220 $atts = array(
221 'trigger' => $atts['status'],
222 'entry_id' => $entry_id,
223 );
224
225 if ( ! isset( $atts['payment'] ) ) {
226 $frm_payment = new FrmTransLitePayment();
227 $atts['payment'] = $frm_payment->get_one_by( $entry_id, 'item_id' );
228 }
229
230 if ( ! isset( $atts['trigger'] ) ) {
231 $atts['trigger'] = $atts['status'];
232 }
233
234 // Set future-cancel as trigger when applicable.
235 $atts['trigger'] = str_replace( '_', '-', $atts['trigger'] );
236
237 /**
238 * Trigger various hooks including frm_payment_status_complete.
239 *
240 * @since 6.25 This was included in the payments submodule, but not included in Lite until 6.25.
241 *
242 * @param array $atts
243 */
244 do_action( 'frm_payment_status_' . $atts['trigger'], $atts );
245
246 if ( $atts['payment'] ) {
247 self::trigger_actions_after_payment( $atts['payment'], $atts );
248 }
249 }
250
251 /**
252 * Maybe trigger payment-success or payment-failed event after payment so actions (like emails) can run.
253 *
254 * @param object $payment
255 * @param array $atts
256 *
257 * @return void
258 */
259 public static function trigger_actions_after_payment( $payment, $atts = array() ) {
260 if ( 'pending' === $payment->status ) {
261 // 3D Secure has a delayed payment status, so avoid sending a payment failed email for a pending payment.
262 return;
263 }
264
265 $entry = FrmEntry::getOne( $payment->item_id );
266 $trigger_event = isset( $atts['trigger'] ) ? 'payment-' . $atts['trigger'] : 'payment-' . $payment->status;
267 $allowed_triggers = array_keys( self::add_payment_trigger( array() ) );
268
269 if ( ! in_array( $trigger_event, $allowed_triggers, true ) ) {
270 $trigger_event = $payment->status === 'complete' ? 'payment-success' : 'payment-failed';
271 }
272
273 FrmFormActionsController::trigger_actions( $trigger_event, $entry->form_id, $entry->id );
274 }
275
276 /**
277 * Filter fields in description.
278 *
279 * @param WP_Post $action
280 * @param array $atts
281 *
282 * @return void
283 */
284 public static function prepare_description( &$action, $atts ) {
285 $description = $action->post_content['description'];
286
287 if ( ! $description ) {
288 return;
289 }
290
291 $atts['value'] = $description;
292 $description = FrmTransLiteAppHelper::process_shortcodes( $atts );
293 $action->post_content['description'] = $description;
294 }
295
296 /**
297 * Convert the amount into 10.00.
298 *
299 * @param mixed $amount
300 * @param array $atts
301 *
302 * @return string
303 */
304 public static function prepare_amount( $amount, $atts = array() ) {
305 if ( isset( $atts['form'] ) ) {
306 $atts['value'] = $amount;
307 $amount = FrmTransLiteAppHelper::process_shortcodes( $atts );
308 }
309
310 if ( is_string( $amount ) && strlen( $amount ) >= 2 && $amount[0] === '[' && str_ends_with( $amount, ']' ) ) {
311 // Make sure we don't use a field id as the amount.
312 $amount = 0;
313 }
314
315 $currency = self::get_currency_for_action( $atts );
316 $total = 0;
317
318 foreach ( (array) $amount as $a ) {
319 $this_amount = self::get_amount_from_string( $a );
320 self::maybe_use_decimal( $this_amount, $currency );
321 self::normalize_number( $this_amount, $currency );
322
323 $total += $this_amount;
324 unset( $a, $this_amount );
325 }
326
327 return number_format( $total, $currency['decimals'], '.', '' );
328 }
329
330 /**
331 * Get currency to use when preparing amount.
332 *
333 * @param array $atts
334 *
335 * @return array
336 */
337 public static function get_currency_for_action( $atts ) {
338 $currency = 'usd';
339
340 if ( isset( $atts['form'] ) ) {
341 $currency = $atts['action']->post_content['currency'];
342 } elseif ( isset( $atts['currency'] ) ) {
343 $currency = $atts['currency'];
344 }
345
346 return FrmCurrencyHelper::get_currency( $currency );
347 }
348
349 /**
350 * @param string $amount
351 *
352 * @return string
353 */
354 private static function get_amount_from_string( $amount ) {
355 $amount = html_entity_decode( $amount );
356 $amount = trim( $amount );
357 preg_match_all( '/[0-9,.]*\.?\,?[0-9]+/', $amount, $matches );
358 return $matches ? end( $matches[0] ) : 0;
359 }
360
361 /**
362 * @param string $amount
363 * @param array $currency
364 *
365 * @return void
366 */
367 private static function maybe_use_decimal( &$amount, $currency ) {
368 if ( $currency['thousand_separator'] !== '.' ) {
369 return;
370 }
371
372 $amount_parts = explode( '.', $amount );
373
374 if ( 2 !== count( $amount_parts ) ) {
375 return;
376 }
377
378 $strlen = strlen( $amount_parts[1] );
379 $used_for_decimal = $strlen === 1 || $strlen === 2;
380
381 if ( $used_for_decimal ) {
382 $amount = str_replace( '.', $currency['decimal_separator'], $amount );
383 }
384 }
385
386 /**
387 * @param string $amount
388 * @param array $currency
389 *
390 * @return void
391 */
392 private static function normalize_number( &$amount, $currency ) {
393 $amount = str_replace( $currency['thousand_separator'], '', $amount );
394 $amount = str_replace( $currency['decimal_separator'], '.', $amount );
395 $amount = number_format( (float) $amount, $currency['decimals'], '.', '' );
396 }
397
398 /**
399 * These settings are included in frm_stripe_vars.settings global JavaScript object on Stripe forms.
400 *
401 * @param int $form_id
402 *
403 * @return array
404 */
405 public static function prepare_settings_for_js( $form_id ) {
406 $payment_actions = self::get_actions_for_form( $form_id );
407 $action_settings = array();
408
409 foreach ( $payment_actions as $payment_action ) {
410 $settings_for_action = array(
411 'id' => $payment_action->ID,
412 'first_name' => $payment_action->post_content['billing_first_name'],
413 'last_name' => $payment_action->post_content['billing_last_name'],
414 'address' => $payment_action->post_content['billing_address'] ?? '',
415 'gateways' => $payment_action->post_content['gateway'],
416 'fields' => self::get_fields_for_price( $payment_action ),
417 'one' => $payment_action->post_content['type'],
418 'email' => $payment_action->post_content['email'],
419 'layout' => $payment_action->post_content['layout'] ?? '',
420 );
421
422 /**
423 * @param array $settings_for_action
424 * @param WP_Post $payment_action
425 */
426 $settings_for_action = apply_filters( 'frm_trans_settings_for_js', $settings_for_action, $payment_action );
427 $action_settings[] = $settings_for_action;
428 }
429
430 return $action_settings;
431 }
432
433 /**
434 * Include the price field ids to pass to the javascript.
435 *
436 * @since 6.5, introduced in v2.0 of the Payments submodule.
437 *
438 * @param WP_Post $action
439 *
440 * @return array|int
441 */
442 private static function get_fields_for_price( $action ) {
443 $amount = $action->post_content['amount'];
444 $shortcodes = FrmFieldsHelper::get_shortcodes( $amount, $action->menu_order );
445 return $shortcodes[2] ?? -1;
446 }
447
448 /**
449 * Get all published payment actions.
450 *
451 * @param int|string $form_id
452 *
453 * @return array
454 */
455 public static function get_actions_for_form( $form_id ) {
456 $action_status = array(
457 'post_status' => 'publish',
458 );
459 $payment_actions = FrmFormAction::get_action_for_form( $form_id, 'payment', $action_status );
460
461 return $payment_actions ? $payment_actions : array();
462 }
463
464 /**
465 * Make sure a gateway field is hidden on the front end.
466 *
467 * @param array $values
468 * @param stdClass $field
469 *
470 * @return array
471 */
472 public static function hide_gateway_field_on_front_end( $values, $field ) {
473 if ( $field->type !== 'gateway' ) {
474 return $values;
475 }
476
477 if ( FrmAppHelper::is_form_builder_page() ) {
478 // The hooks this uses can get called in the form builder and settings pages.
479 // But we do not need the script in this case.
480 return $values;
481 }
482
483 // This is also called from the frm_enqueue_form_scripts hook.
484 // With this here, the value of frm_stripe_vars.settings[0].fields is -1
485 // This is because the amount value is processed and a shortcode is not found in '000'.
486 FrmStrpLiteActionsController::load_scripts( (int) $field->form_id );
487 FrmSquareLiteActionsController::load_scripts( (int) $field->form_id );
488
489 $values['type'] = 'hidden';
490 return $values;
491 }
492
493 /**
494 * Entries are deleted on payment failure so set the form values after an error from the entry data that gets deleted.
495 *
496 * @since 6.5.1
497 *
498 * @param array $values
499 * @param stdClass $field
500 *
501 * @return array
502 */
503 public static function fill_entry_from_previous( $values, $field ) {
504 global $frm_vars;
505 $previous_entry = $frm_vars['frm_trans']['pay_entry'] ?? false;
506
507 // phpcs:ignore Universal.Operators.StrictComparisons
508 if ( ! $previous_entry || $previous_entry->form_id != $field->form_id ) {
509 return $values;
510 }
511
512 if ( is_array( $previous_entry->metas ) && isset( $previous_entry->metas[ $field->id ] ) ) {
513 $values['value'] = $previous_entry->metas[ $field->id ];
514 }
515
516 $frm_vars['trans_filled'] = true;
517 $previous_entry_id = $previous_entry->id;
518 self::destroy_entry_later( $previous_entry_id );
519
520 return $values;
521 }
522
523 /**
524 * Destroy an entry, but delay it to happen when the form is displayed.
525 * It needs to happen late enough that FrmProNestedFormsController::display_single_iteration_of_nested_form is able to fill in data for repeater fields.
526 * See Formidable Stripe issue #136 for more information.
527 *
528 * @since 6.5.1
529 *
530 * @param int|string $entry_id
531 *
532 * @return void
533 */
534 private static function destroy_entry_later( $entry_id ) {
535 if ( in_array( (int) $entry_id, self::$entry_ids_to_destroy_later, true ) ) {
536 // Avoid trying to delete this multiple times as fill_entry_from_previous is called more than once.
537 return;
538 }
539
540 $destroy_callback =
541 /**
542 * Destroy an entry and remove this action so it only tries to destroy the entry once.
543 *
544 * @param int|string $entry_id
545 * @param Closure $destroy_callback
546 *
547 * @return void
548 */
549 function () use ( $entry_id, &$destroy_callback ) {
550 FrmEntry::destroy( $entry_id );
551 // Only call this once.
552 remove_action( 'frm_entry_form', $destroy_callback );
553 };
554 add_action( 'frm_entry_form', $destroy_callback );
555
556 self::$entry_ids_to_destroy_later[] = (int) $entry_id;
557 }
558
559 /**
560 * Filter payment action on save.
561 *
562 * @since 6.22
563 *
564 * @param array $settings
565 * @param array $action
566 *
567 * @return array
568 */
569 public static function before_save_settings( $settings, $action ) {
570 $settings['gateway'] = ! empty( $settings['gateway'] ) ? (array) $settings['gateway'] : array( 'stripe' );
571
572 if ( in_array( 'square', $settings['gateway'], true ) ) {
573 $currency = FrmSquareLiteConnectHelper::get_merchant_currency();
574 $settings['currency'] = false !== $currency ? strtolower( $currency ) : 'usd';
575 } else {
576 $settings['currency'] = strtolower( $settings['currency'] );
577 }
578
579 $form_id = absint( $action['menu_order'] );
580
581 if ( empty( $settings['credit_card'] ) ) {
582 $credit_card_field_id = FrmDb::get_var(
583 'frm_fields',
584 array(
585 'type' => 'credit_card',
586 'form_id' => $form_id,
587 )
588 );
589
590 if ( ! $credit_card_field_id ) {
591 $credit_card_field_id = self::add_a_credit_card_field( $form_id );
592 }
593
594 if ( $credit_card_field_id ) {
595 $settings['credit_card'] = $credit_card_field_id;
596 }
597 }
598
599 if ( ! in_array( 'stripe', $settings['gateway'], true ) ) {
600 // We only need a gateway field for Stripe add-on compatibility,
601 // so unless Stripe is selected, we can return early.
602 return $settings;
603 }
604
605 $gateway_field_id = FrmDb::get_var(
606 'frm_fields',
607 array(
608 'type' => 'gateway',
609 'form_id' => $form_id,
610 )
611 );
612
613 if ( ! $gateway_field_id ) {
614 self::add_a_gateway_field( $form_id );
615 }
616
617 return $settings;
618 }
619
620 /**
621 * A credit card field is added automatically if missing before a Stripe action is updated.
622 *
623 * @param int $form_id
624 *
625 * @return false|int
626 */
627 protected static function add_a_credit_card_field( $form_id ) {
628 return self::add_a_field( $form_id, 'credit_card', __( 'Payment', 'formidable' ) );
629 }
630
631 /**
632 * A gateway field is added automatically for compatibility with the Stripe add on.
633 * The gateway field is not important for the Stripe Lite implementation.
634 *
635 * @param int $form_id
636 *
637 * @return false|int
638 */
639 protected static function add_a_gateway_field( $form_id ) {
640 return self::add_a_field( $form_id, 'gateway', __( 'Payment Method', 'formidable' ) );
641 }
642
643 /**
644 * @param int $form_id
645 * @param string $field_type
646 * @param string $field_name
647 *
648 * @return false|int
649 */
650 protected static function add_a_field( $form_id, $field_type, $field_name ) {
651 $new_values = FrmFieldsHelper::setup_new_vars( $field_type, $form_id );
652 $new_values['name'] = $field_name;
653 $new_values['field_order'] = self::get_field_order_before_submit( $form_id, $new_values['field_order'] );
654 return FrmField::create( $new_values );
655 }
656
657 /**
658 * When auto-injecting a field, ensure it is placed before the submit button.
659 *
660 * @since 6.29
661 *
662 * @param int $form_id
663 * @param int $field_order
664 *
665 * @return int
666 */
667 private static function get_field_order_before_submit( $form_id, $field_order ) {
668 $submit_field = FrmSubmitHelper::get_submit_field( $form_id );
669
670 if ( ! $submit_field || $field_order < (int) $submit_field->field_order ) {
671 return $field_order;
672 }
673
674 $submit_order = (int) $submit_field->field_order;
675 FrmField::update( $submit_field->id, array( 'field_order' => $submit_order + 1 ) );
676 return $submit_order;
677 }
678
679 /**
680 * Remove credit card validation errors.
681 *
682 * @param array $errors
683 * @param stdClass $field
684 *
685 * @return array
686 */
687 public static function remove_cc_errors( $errors, $field ) {
688 $field_id = $field->temp_id ?? $field->id;
689
690 if ( isset( $errors[ 'field' . $field_id . '-cc' ] ) ) {
691 unset( $errors[ 'field' . $field_id . '-cc' ] );
692 }
693
694 if ( isset( $errors[ 'field' . $field_id ] ) ) {
695 unset( $errors[ 'field' . $field_id ] );
696 }
697
698 return $errors;
699 }
700 }
701