PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.5
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.5
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 / models / FrmStrpLiteAuth.php

FrmStrpLiteAuth.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.5, at stripe/models/FrmStrpLiteAuth.php

731 lines 21.5 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 FrmStrpLiteAuth {
7
8 /**
9 * Payment details are stored after checking the request params.
10 * The details are then accessed later in self::maybe_show_message.
11 * These details include entry, intent, and payment.
12 *
13 * @var array
14 */
15 private static $details_by_form_id = array();
16
17 /**
18 * If returning from Stripe to authorize a payment, show the message.
19 * This is used for 3D secure and for Stripe link.
20 *
21 * @since 6.5, introduced in v2.0 of the Stripe add on.
22 *
23 * @param string $html Form HTML that gets filtered through frm_filter_final_form.
24 * @return string
25 */
26 public static function maybe_show_message( $html ) {
27 $link_error = FrmAppHelper::simple_get( 'frm_link_error' );
28 if ( $link_error ) {
29 $message = '<div class="frm_error_style">' . self::get_message_for_stripe_link_code( $link_error ) . '</div>';
30 self::insert_error_message( $message, $html );
31 return $html;
32 }
33
34 $form_id = self::check_html_for_form_id_match( $html );
35 if ( false === $form_id ) {
36 return $html;
37 }
38
39 $details = self::$details_by_form_id[ $form_id ];
40 $atts = array(
41 'fields' => FrmFieldsHelper::get_form_fields( $form_id ),
42 'entry' => $details['entry'],
43 );
44 self::prepare_success_atts( $atts );
45
46 $intent = $details['intent'];
47 $payment = $details['payment'];
48
49 if ( in_array( $intent->status, array( 'requires_source', 'requires_payment_method', 'canceled' ), true ) ) {
50 $message = '<div class="frm_error_style">' . $intent->last_payment_error->message . '</div>';
51 self::insert_error_message( $message, $html );
52 return $html;
53 }
54
55 $intent_is_processing = 'processing' === $intent->status;
56 if ( $intent_is_processing ) {
57 // Append an additional processing message to the end of the success message.
58 $filter = function( $message ) {
59 $stripe_settings = FrmStrpLiteAppHelper::get_settings();
60 $message .= '<p>' . esc_html( $stripe_settings->settings->processing_message ) . '</p>';
61 return $message;
62 };
63 add_filter( 'frm_content', $filter );
64 }
65
66 ob_start();
67 FrmFormsController::run_success_action( $atts );
68 $message = ob_get_contents();
69 ob_end_clean();
70
71 // Clean up the filter we added above so no other success messages get altered if there are multiple forms.
72 if ( $intent_is_processing && isset( $filter ) ) {
73 remove_filter( 'frm_content', $filter );
74 }
75
76 return $message;
77 }
78
79 /**
80 * Check the URL params for Stripe intent details.
81 * When these params are detected, the form is replaced with a success message.
82 * These params are used in 3D secure as well as Stripe Link.
83 *
84 * The params include:
85 * - The ID of the payment intent or setup intent.
86 * - The ID of the entry.
87 * - The client secret which is used to verify the intent.
88 * - The charge ID (if applicable)
89 *
90 * @since 6.5
91 *
92 * @param string|int $form_id
93 * @return array|false
94 */
95 private static function check_request_params( $form_id ) {
96 $form_id = (int) $form_id;
97 $intent_id = FrmAppHelper::simple_get( 'payment_intent' );
98 $is_setup_intent = false;
99
100 if ( ! $intent_id ) {
101 $intent_id = FrmAppHelper::simple_get( 'setup_intent' );
102 if ( ! $intent_id ) {
103 return false;
104 }
105
106 $is_setup_intent = true;
107 }
108
109 $entry_id = FrmAppHelper::simple_get( 'frmstrp', 'absint', 0 );
110 if ( ! $entry_id ) {
111 return false;
112 }
113
114 $entry = FrmEntry::getOne( $entry_id );
115 if ( ! $entry || (int) $entry->form_id !== $form_id ) {
116 return false;
117 }
118
119 $charge_id = FrmAppHelper::simple_get( 'charge' );
120 $has_charge = (bool) $charge_id;
121 $frm_payment = new FrmTransLitePayment();
122
123 if ( $has_charge ) {
124 // Stripe link payments use charge id.
125 $payment = $frm_payment->get_one_by( $charge_id, 'receipt_id' );
126 } else {
127 // 3D secure payments use intent id.
128 $payment = $frm_payment->get_one_by( $intent_id, 'receipt_id' );
129 }
130
131 if ( ! $payment || (int) $payment->item_id !== (int) $entry->id ) {
132 return false;
133 }
134
135 if ( ! FrmStrpLiteAppHelper::stripe_is_configured() ) {
136 return false;
137 }
138
139 $intent_function_name = $is_setup_intent ? 'get_setup_intent' : 'get_intent';
140 $intent = FrmStrpLiteAppHelper::call_stripe_helper_class( $intent_function_name, $intent_id );
141
142 if ( ! $intent || ! self::verify_client_secret( $intent, $is_setup_intent ) ) {
143 return false;
144 }
145
146 self::$details_by_form_id[ $form_id ] = array(
147 'entry' => $entry,
148 'intent' => $intent,
149 'payment' => $payment,
150 );
151
152 return self::$details_by_form_id[ $form_id ];
153 }
154
155 /**
156 * The frm_filter_final_form filter only passes form HTML as a string.
157 * To determine which form is being filtered, this function checks for the
158 * hidden form_id input. If there is a match, it returns the matching form id.
159 *
160 * @since 6.5
161 *
162 * @param string $html
163 * @return int|false Matching form id or false if there is no match.
164 */
165 private static function check_html_for_form_id_match( $html ) {
166 if ( empty( self::$details_by_form_id ) ) {
167 return false;
168 }
169
170 $form_ids = array_keys( self::$details_by_form_id );
171 foreach ( $form_ids as $form_id ) {
172 $substring = '<input type="hidden" name="form_id" value="' . $form_id . '"';
173 if ( strpos( $html, $substring ) ) {
174 return $form_id;
175 }
176 }
177
178 return false;
179 }
180
181 /**
182 * Check the client secret in the URL, verify it matches the Stripe object and isn't being manipulated.
183 *
184 * @since 6.5, introduced in v3.0 of the Stripe add on.
185 *
186 * @param object $intent
187 * @param bool $is_setup_intent
188 * @return bool True if the client secret is set and valid.
189 */
190 private static function verify_client_secret( $intent, $is_setup_intent ) {
191 $client_secret_param = $is_setup_intent ? 'setup_intent_client_secret' : 'payment_intent_client_secret';
192 $client_secret = FrmAppHelper::simple_get( $client_secret_param );
193 return $client_secret && $client_secret === $intent->client_secret;
194 }
195
196 /**
197 * Translate an error code into a readable message for the front end.
198 * FrmStrpLiteLinkRedirectHelper uses these codes to redirect errors that are then handled in self::maybe_show_message.
199 *
200 * @since 6.5, introduced in v3.0 of the Stripe add on.
201 *
202 * @param string $code
203 * @return string
204 */
205 private static function get_message_for_stripe_link_code( $code ) {
206 switch ( $code ) {
207 case 'intent_does_not_exist':
208 return __( 'Payment intent does not exist.', 'formidable' );
209 case 'unable_to_verify':
210 return __( 'Unable to verify payment intent.', 'formidable' );
211 case 'did_not_complete':
212 return __( 'Payment did not complete.', 'formidable' );
213 case 'no_payment_record':
214 return __( 'Unable to find record of payment.', 'formidable' );
215 case 'no_entry_found':
216 return __( 'This form submission does not exist.', 'formidable' );
217 case 'no_stripe_link_action':
218 return __( 'This form is not configured for Stripe link payments.', 'formidable' );
219 case 'create_subscription_failed':
220 return __( 'Something went wrong when trying to create a subscription.', 'formidable' );
221 case 'payment_failed':
222 return __( 'Payment was not successfully processed.', 'formidable' );
223 }
224 return '';
225 }
226
227 /**
228 * Add the parameters the receiving functions are expecting.
229 *
230 * @since 6.5, introduced in v2.0 of the Stripe add on.
231 *
232 * @param array $atts
233 * @return void
234 */
235 private static function prepare_success_atts( &$atts ) {
236 $atts['form'] = FrmForm::getOne( $atts['entry']->form_id );
237 $atts['entry_id'] = $atts['entry']->id;
238 $opt = 'success_action';
239 $atts['conf_method'] = ! empty( $atts['form']->options[ $opt ] ) ? $atts['form']->options[ $opt ] : 'message';
240 }
241
242 /**
243 * Insert a message/error where the form styling will be applied.
244 *
245 * @since 6.5, introduced in v2.0 of the Stripe add on.
246 */
247 private static function insert_error_message( $message, &$form ) {
248 $add_after = '<fieldset>';
249 $pos = strpos( $form, $add_after );
250 if ( $pos !== false ) {
251 $form = substr_replace( $form, $add_after . $message, $pos, strlen( $add_after ) );
252 }
253 }
254
255 /**
256 * Include the token if going between pages.
257 *
258 * @param object $form The form being submitted.
259 * @return void
260 */
261 public static function add_hidden_token_field( $form ) {
262 $posted_form = FrmAppHelper::get_param( 'form_id', 0, 'post', 'absint' );
263 if ( $posted_form != $form->id || FrmFormsController::just_created_entry( $form->id ) ) {
264 // Check to make sure the correct form was submitted.
265 // Was an entry already created and the form should be loaded fresh?
266
267 $intents = self::maybe_create_intents( $form->id );
268 self::include_intents_in_form( $intents, $form );
269
270 return;
271 }
272
273 $intents = self::get_payment_intents( 'frmintent' . $form->id );
274 if ( ! empty( $intents ) ) {
275 self::update_intent_pricing( $form->id, $intents );
276 } else {
277 $intents = self::maybe_create_intents( $form->id );
278 }
279
280 self::include_intents_in_form( $intents, $form );
281 }
282
283 /**
284 * Include hidden fields with payment intent IDs in the form.
285 *
286 * @since 6.5, introduced in v2.02 of the Stripe add on.
287 *
288 * @param array $intents
289 * @param stdClass $form
290 * @return void
291 */
292 private static function include_intents_in_form( $intents, $form ) {
293 foreach ( $intents as $intent ) {
294 if ( is_array( $intent ) ) {
295 $id = $intent['id'];
296 $action = $intent['action'];
297 } else {
298 $id = $intent;
299 $action = '';
300 }
301
302 echo '<input type="hidden" name="frmintent' . esc_attr( $form->id ) . '[]" value="' . esc_attr( $id ) . '" data-action="' . esc_attr( $action ) . '" />';
303 }
304 }
305
306 /**
307 * Check POST data for payment intents.
308 *
309 * @since 6.5, introduced in v2.0 of the Stripe add on.
310 *
311 * @param string $name
312 * @return mixed
313 */
314 public static function get_payment_intents( $name ) {
315 // phpcs:ignore WordPress.Security.NonceVerification.Missing
316 if ( ! isset( $_POST[ $name ] ) ) {
317 return array();
318 }
319 $intents = $_POST[ $name ]; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.NonceVerification.Missing
320 FrmAppHelper::sanitize_value( 'sanitize_text_field', $intents );
321 return $intents;
322 }
323
324 /**
325 * Update pricing before authorizing.
326 *
327 * @since 6.5, introduced in v2.0 of the Stripe add on.
328 *
329 * @return void
330 */
331 public static function update_intent_ajax() {
332 check_ajax_referer( 'frm_strp_ajax', 'nonce' );
333
334 if ( empty( $_POST['form'] ) ) {
335 wp_die();
336 }
337
338 $form = json_decode( stripslashes( $_POST['form'] ), true ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
339 if ( ! is_array( $form ) ) {
340 wp_die();
341 }
342
343 self::format_form_data( $form );
344
345 $form_id = absint( $form['form_id'] );
346 $intents = isset( $form[ 'frmintent' . $form_id ] ) ? $form[ 'frmintent' . $form_id ] : array();
347
348 if ( empty( $intents ) ) {
349 wp_die();
350 }
351
352 if ( ! is_array( $intents ) ) {
353 $intents = array( $intents );
354 } else {
355 foreach ( $intents as $k => $intent ) {
356 if ( is_array( $intent ) && isset( $intent[ $k ] ) ) {
357 $intents[ $k ] = $intent[ $k ];
358 }
359 }
360 }
361
362 $_POST = $form;
363 self::update_intent_pricing( $form_id, $intents );
364
365 wp_die();
366 }
367
368 /**
369 * Update pricing on page turn and non-ajax validation.
370 *
371 * @since 6.5, introduced in v2.0 of the Stripe add on.
372 * @param int $form_id
373 * @param array $intents
374 * @return void
375 */
376 private static function update_intent_pricing( $form_id, &$intents ) {
377 // phpcs:ignore WordPress.Security.NonceVerification.Missing
378 if ( ! isset( $_POST['form_id'] ) || absint( $_POST['form_id'] ) != $form_id ) {
379 return;
380 }
381
382 $actions = FrmStrpLiteActionsController::get_actions_before_submit( $form_id );
383 if ( empty( $actions ) || empty( $intents ) ) {
384 return;
385 }
386
387 $form = FrmForm::getOne( $form_id );
388
389 try {
390 if ( ! FrmStrpLiteAppHelper::call_stripe_helper_class( 'initialize_api' ) ) {
391 return;
392 }
393 } catch ( Exception $e ) {
394 // Intent was not created.
395 return;
396 }
397
398 foreach ( $intents as $k => $intent ) {
399 $intent_id = explode( '_secret_', $intent )[0];
400 $is_setup_intent = 0 === strpos( $intent_id, 'seti_' );
401 if ( $is_setup_intent ) {
402 continue;
403 }
404
405 $saved = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_intent', $intent_id );
406 foreach ( $actions as $action ) {
407 if ( $saved->metadata->action != $action->ID ) {
408 continue;
409 }
410 $intents[ $k ] = array(
411 'id' => $intent,
412 'action' => $action->ID,
413 );
414
415 $amount = $action->post_content['amount'];
416 if ( strpos( $amount, '[' ) === false ) {
417 // The amount is static, so it doesn't need an update.
418 continue;
419 }
420
421 // Update amount based on field shortcodes.
422 $entry = self::generate_false_entry();
423 $amount = FrmStrpLiteActionsController::prepare_amount( $amount, compact( 'form', 'entry', 'action' ) );
424 if ( $saved->amount == $amount || $amount == '000' ) {
425 continue;
426 }
427
428 FrmStrpLiteAppHelper::call_stripe_helper_class( 'update_intent', $intent_id, array( 'amount' => $amount ) );
429 }
430 }
431 }
432
433 /**
434 * Create an entry object with posted values.
435 *
436 * @since 6.5, introduced in v2.0 of the Stripe add on.
437 * @return stdClass
438 */
439 private static function generate_false_entry() {
440 $entry = new stdClass();
441 $entry->post_id = 0;
442 $entry->id = 0;
443 $entry->metas = array();
444
445 // phpcs:ignore WordPress.Security.NonceVerification.Missing
446 foreach ( $_POST as $k => $v ) {
447 $k = sanitize_text_field( stripslashes( $k ) );
448 $v = wp_unslash( $v );
449
450 if ( $k === 'item_meta' ) {
451 foreach ( $v as $f => $value ) {
452 FrmAppHelper::sanitize_value( 'wp_kses_post', $value );
453 $entry->metas[ absint( $f ) ] = $value;
454 }
455 } else {
456 FrmAppHelper::sanitize_value( 'wp_kses_post', $v );
457 $entry->{$k} = $v;
458 }
459 }
460
461 return $entry;
462 }
463
464 /**
465 * Reformat the form data in name => value array.
466 *
467 * @since 6.5, introduced in v2.0 of the Stripe add on.
468 *
469 * @param array $form
470 * @return void
471 */
472 private static function format_form_data( &$form ) {
473 $formatted = array();
474
475 foreach ( $form as $input ) {
476 $key = $input['name'];
477 if ( isset( $formatted[ $key ] ) ) {
478 if ( is_array( $formatted[ $key ] ) ) {
479 $formatted[ $key ][] = $input['value'];
480 } else {
481 $formatted[ $key ] = array( $formatted[ $key ], $input['value'] );
482 }
483 } else {
484 $formatted[ $key ] = $input['value'];
485 }
486 }
487
488 parse_str( http_build_query( $formatted ), $form );
489 }
490
491 /**
492 * Create intents on form load when required.
493 * This only happens in two cases: For stripe link, and when processing a one-time payment before the entry is created.
494 *
495 * @since 6.5, introduced in v2.0 of the Stripe add on.
496 *
497 * @param string|int $form_id
498 * @return array
499 */
500 private static function maybe_create_intents( $form_id ) {
501 $intents = array();
502
503 $details = self::check_request_params( $form_id );
504 if ( is_array( $details ) ) {
505 // Exit early if the request params are set.
506 // This way an extra payment intent isn't created for Stripe Link.
507 return $intents;
508 }
509
510 if ( ! FrmStrpLiteAppHelper::call_stripe_helper_class( 'initialize_api' ) ) {
511 // Stripe is not configured, so don't create intents.
512 return $intents;
513 }
514
515 $actions = FrmStrpLiteActionsController::get_actions_before_submit( $form_id );
516 self::add_amount_to_actions( $form_id, $actions );
517
518 foreach ( $actions as $action ) {
519 $intent = self::create_intent( $action );
520 if ( ! is_object( $intent ) ) {
521 // A non-object is a string error message.
522 // The error gets logged to results.log so we can just skip it.
523 // Reasons it could fail is because a payment method type was specified that will not work.
524 // A payment method type may not work because of a currency conflict, or because it isn't enabled.
525 // Or the payment method type could be an incorrect value.
526 // When using Stripe Connect, the error will just say "Unable to create intent".
527 // In this case, you can find the full error message in the Stripe dashboard.
528 continue;
529 }
530
531 $intents[] = array(
532 'id' => $intent->client_secret,
533 'action' => $action->ID,
534 );
535 }
536
537 return $intents;
538 }
539
540 /**
541 * Create a payment intent for Stripe link or when processing a payment before the entry is created.
542 *
543 * @since 3.0 This code was moved out of self::maybe_create_intents into a new function.
544 *
545 * @param WP_Post $action
546 * @return mixed
547 */
548 private static function create_intent( $action ) {
549 $amount = $action->post_content['amount'];
550 if ( $amount == '000' ) {
551 $amount = 100; // Create the intent when the form loads.
552 }
553
554 if ( 'recurring' === $action->post_content['type'] ) {
555 $payment_method_types = FrmStrpLitePaymentTypeHandler::get_payment_method_types( $action );
556 return self::create_setup_intent( $payment_method_types );
557 }
558
559 $new_charge = array(
560 'amount' => $amount,
561 'currency' => $action->post_content['currency'],
562 'metadata' => array( 'action' => $action->ID ),
563 );
564
565 if ( FrmStrpLitePaymentTypeHandler::should_use_automatic_payment_methods( $action ) ) {
566 $new_charge['automatic_payment_methods'] = array( 'enabled' => true );
567 } else {
568 $payment_method_types = FrmStrpLitePaymentTypeHandler::get_payment_method_types( $action );
569 $new_charge['payment_method_types'] = $payment_method_types;
570 }
571
572 return FrmStrpLiteAppHelper::call_stripe_helper_class( 'create_intent', $new_charge );
573 }
574
575 /**
576 * Create a customer and an associated setup intent for a recurring Stripe link payment.
577 *
578 * @since 6.5, introduced in v3.0 of the Stripe add on.
579 *
580 * @param array $payment_method_types
581 * @return object|false
582 */
583 private static function create_setup_intent( $payment_method_types ) {
584 $payment_info = array(
585 'user_id' => FrmTransLiteAppHelper::get_user_id_for_current_payment(),
586 );
587
588 // We need to add a customer to support subscriptions with link.
589 $customer = FrmStrpLiteAppHelper::call_stripe_helper_class( 'get_customer', $payment_info );
590 if ( ! is_object( $customer ) ) {
591 return false;
592 }
593
594 return FrmStrpLiteAppHelper::call_stripe_helper_class( 'create_setup_intent', $customer->id, $payment_method_types );
595 }
596
597 /**
598 * @since 6.5, introduced in v2.0 of the Stripe add on.
599 *
600 * @param string|int $form_id
601 * @param array $actions
602 * @return void
603 */
604 private static function add_amount_to_actions( $form_id, &$actions ) {
605 if ( empty( $actions ) ) {
606 return;
607 }
608 $form = FrmForm::getOne( $form_id );
609
610 foreach ( $actions as $k => $action ) {
611 $amount = self::get_amount_before_submit( compact( 'action', 'form' ) );
612 $actions[ $k ]->post_content['amount'] = $amount;
613 }
614 }
615
616 /**
617 * @since 6.5, introduced in v2.0 of the Stripe add on.
618 *
619 * @param array $atts
620 * @return string
621 */
622 private static function get_amount_before_submit( $atts ) {
623 $amount = $atts['action']->post_content['amount'];
624 return FrmStrpLiteActionsController::prepare_amount( $atts['action']->post_content['amount'], $atts );
625 }
626
627 /**
628 * Get the URL to return to after a payment is complete.
629 * This may either use the success URL on redirect, or the message on success.
630 * It shouldn't be confused for the Stripe link return URL. It isn't used for that. That uses the frmstrplinkreturn AJAX action instead.
631 *
632 * @since 6.5, introduced in v2.0 of the Stripe add on.
633 *
634 * @param array $atts
635 * @return string
636 */
637 public static function return_url( $atts ) {
638 $atts = array(
639 'entry' => $atts['entry'],
640 );
641 self::prepare_success_atts( $atts );
642
643 if ( $atts['conf_method'] === 'redirect' ) {
644 $redirect = self::get_redirect_url( $atts );
645 } else {
646 $redirect = self::get_message_url( $atts );
647 }
648
649 return $redirect;
650 }
651
652 /**
653 * If the form should redirect, get the url to redirect to.
654 *
655 * @since 6.5, introduced in v2.0 of the Stripe add on.
656 *
657 * @param array $atts {
658 * @type stdClass $form
659 * @type stdClass $entry
660 * }
661 * @return string
662 */
663 private static function get_redirect_url( $atts ) {
664 $success_url = trim( $atts['form']->options['success_url'] );
665 $success_url = apply_filters( 'frm_content', $success_url, $atts['form'], $atts['entry'] );
666 $success_url = do_shortcode( $success_url );
667 $atts['id'] = $atts['entry']->id;
668
669 add_filter( 'frm_redirect_url', 'FrmEntriesController::prepare_redirect_url' );
670 return apply_filters( 'frm_redirect_url', $success_url, $atts['form'], $atts );
671 }
672
673 /**
674 * If the form should should a message, apend it to the success url.
675 *
676 * @since 6.5, introduced in v2.0 of the Stripe add on.
677 *
678 * @param array $atts
679 */
680 private static function get_message_url( $atts ) {
681 $url = self::get_referer_url( $atts['entry_id'], false );
682 if ( false === $url ) {
683 $url = FrmAppHelper::get_server_value( 'HTTP_REFERER' );
684 }
685 return add_query_arg( array( 'frmstrp' => $atts['entry_id'] ), $url );
686 }
687
688 /**
689 * @since 6.5
690 *
691 * @param string|int $entry_id
692 * @param bool $delete_meta
693 * @return string|false
694 */
695 public static function get_referer_url( $entry_id, $delete_meta = true ) {
696 $row = FrmDb::get_row(
697 'frm_item_metas',
698 array(
699 'field_id' => 0,
700 'item_id' => $entry_id,
701 'meta_value LIKE' => '{"referer":',
702 ),
703 'id, meta_value'
704 );
705 if ( ! $row ) {
706 return false;
707 }
708
709 $meta = $row->meta_value;
710 $meta = json_decode( $meta, true );
711
712 if ( ! is_array( $meta ) || empty( $meta['referer'] ) ) {
713 return false;
714 }
715
716 self::delete_temporary_referer_meta( (int) $row->id );
717 return $meta['referer'];
718 }
719
720 /**
721 * Delete the referer meta as we'll no longer need it.
722 *
723 * @param int $row_id
724 * @return void
725 */
726 private static function delete_temporary_referer_meta( $row_id ) {
727 global $wpdb;
728 $wpdb->delete( $wpdb->prefix . 'frm_item_metas', array( 'id' => $row_id ) );
729 }
730 }
731