PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.22.2
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.22.2
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 / square / helpers / FrmSquareLiteConnectHelper.php

FrmSquareLiteConnectHelper.php in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.22.2, at square/helpers/FrmSquareLiteConnectHelper.php

713 lines 20.1 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 FrmSquareLiteConnectHelper {
7
8 /**
9 * Track the latest error when calling the Square API.
10 *
11 * @since 6.22
12 *
13 * @var string|null
14 */
15 public static $latest_error_from_square_api;
16
17 /**
18 * @return void
19 */
20 public static function render_settings_container() {
21 $settings = FrmSquareLiteAppHelper::get_settings();
22
23 self::register_settings_scripts();
24
25 ?>
26 <table class="form-table" style="width: 400px;">
27 <tr class="form-field">
28 <td>
29 <?php esc_html_e( 'Test Mode', 'formidable' ); ?>
30 </td>
31 <td>
32 <label>
33 <input type="checkbox" name="frm_square_test_mode" id="frm_square_test_mode" value="1" <?php checked( $settings->settings->test_mode, 1 ); ?> />
34 <?php esc_html_e( 'Use the Square test mode', 'formidable' ); ?>
35 </label>
36 </td>
37 </tr>
38 </table>
39
40 <div>
41 <div class="frm_grid_container">
42 <?php
43
44 $modes = array( 'live', 'test' );
45 foreach ( $modes as $mode ) {
46 self::render_settings_for_mode( $mode );
47 }
48 ?>
49 </div>
50 </div>
51 <?php if ( ! is_ssl() ) { ?>
52 <div>
53 <em>
54 <?php esc_html_e( 'Your site is not using SSL. Before using Square to collect payments, you will need to install an SSL certificate on your site.', 'formidable' ); // phpcs:ignore SlevomatCodingStandard.Files.LineLength.LineTooLong ?>
55 </em>
56 </div>
57 <?php } ?>
58 <?php
59 }
60
61 /**
62 * @param string $mode
63 * @return void
64 */
65 private static function render_settings_for_mode( $mode ) {
66 ?>
67 <div class="frm-card-item frm4">
68 <div class="frm-flex-col">
69 <div>
70 <span style="font-size: var(--text-lg); font-weight: 500; margin-right: 5px;">
71 <?php
72 echo $mode === 'test' ? esc_html__( 'Test', 'formidable' ) : esc_html__( 'Live', 'formidable' );
73 ?>
74 </span>
75 <?php
76
77 $connected = (bool) self::get_merchant_id( $mode );
78
79 $tag_classes = '';
80 if ( $connected ) {
81 $tag_classes = 'frm-lt-green-tag';
82 } else {
83 $tag_classes = 'frm-grey-tag';
84 }
85 ?>
86 <div class="frm-meta-tag <?php echo esc_attr( $tag_classes ); ?>" style="font-size: var(--text-sm); font-weight: 600;">
87 <?php
88 if ( $connected ) {
89 FrmAppHelper::icon_by_class( 'frm_icon_font frm_checkmark_icon', array( 'style' => 'width: 10px; position: relative; top: 2px; margin-right: 5px;' ) );
90 echo 'Connected';
91 } else {
92 echo 'Not configured';
93 }
94 ?>
95 </div>
96 </div>
97 <div style="margin-top: 5px; flex: 1;">
98 <?php
99 if ( 'live' === $mode ) {
100 esc_html_e( 'Live version to process real customer transactions', 'formidable' );
101 } else {
102 esc_html_e( 'Simulate payments and ensure everything works smoothly before going live.', 'formidable' );
103 }
104 ?>
105 </div>
106 <div class="frm-card-bottom">
107 <?php if ( $connected ) { ?>
108 <a id="frm_disconnect_square_<?php echo esc_attr( $mode ); ?>" class="button-secondary frm-button-secondary" href="#">
109 <?php esc_html_e( 'Disconnect', 'formidable' ); ?>
110 </a>
111 <?php } else { ?>
112 <a class="frm-connect-square-with-oauth button-secondary frm-button-secondary" data-mode="<?php echo esc_attr( $mode ); ?>" href="#">
113 <?php esc_html_e( 'Connect', 'formidable' ); ?>
114 </a>
115 <?php } ?>
116 </div>
117 </div>
118 </div>
119 <?php
120 }
121
122 /**
123 * @return void
124 */
125 private static function register_settings_scripts() {
126 $script_url = FrmSquareLiteAppHelper::plugin_url() . '/js/settings.js';
127 $dependencies = array( 'formidable_dom' );
128 $plugin_version = FrmAppHelper::plugin_version();
129 wp_register_script( 'formidable_square_settings', $script_url, $dependencies, $plugin_version, true );
130 wp_enqueue_script( 'formidable_square_settings' );
131 }
132
133 public static function get_oauth_redirect_url() {
134 $mode = FrmAppHelper::get_post_param( 'mode', 'test', 'sanitize_text_field' );
135
136 if ( self::get_merchant_id( $mode ) ) {
137 // Do not allow for initialize if there is already a configured account id.
138 return false;
139 }
140
141 $additional_body = array(
142 'password' => self::generate_client_password( $mode ),
143 'user_id' => get_current_user_id(),
144 'frm_square_api_mode' => $mode,
145 );
146
147 // Clear the transient so it doesn't fail.
148 delete_option( 'frm_square_lite_last_verify_attempt' );
149 $data = self::post_to_connect_server( 'oauth_request', $additional_body );
150
151 if ( is_string( $data ) ) {
152 return false;
153 }
154
155 if ( ! empty( $data->password ) ) {
156 update_option( self::get_server_side_token_option_name( $mode ), $data->password, 'no' );
157 }
158
159 if ( ! is_object( $data ) || empty( $data->redirect_url ) ) {
160 return false;
161 }
162
163 return $data->redirect_url;
164 }
165
166 /**
167 * @param string $action
168 * @param array $additional_body
169 * @return object|string
170 */
171 private static function post_to_connect_server( $action, $additional_body = array() ) {
172 $body = array(
173 'frm_square_api_action' => $action,
174 'frm_square_api_mode' => FrmSquareLiteAppHelper::active_mode(),
175 );
176 $body = array_merge( $body, $additional_body );
177 $url = self::get_url_to_connect_server();
178 $headers = self::build_headers_for_post();
179
180 if ( ! $headers ) {
181 return 'Unable to build headers for post. Is your pro license configured properly?';
182 }
183
184 // (Seconds) default timeout is 5. we want a bit more time to work with.
185 $timeout = 45;
186
187 self::try_to_extend_server_timeout( $timeout );
188
189 $args = compact( 'body', 'headers', 'timeout' );
190 $response = wp_remote_post( $url, $args );
191
192 if ( ! self::validate_response( $response ) ) {
193 return 'Response from server is invalid';
194 }
195
196 $body = self::pull_response_body( $response );
197 if ( empty( $body->success ) ) {
198 if ( ! empty( $body->data ) && is_string( $body->data ) ) {
199 return $body->data;
200 }
201 return 'Response from server was not successful';
202 }
203
204 return isset( $body->data ) ? $body->data : array();
205 }
206
207 private static function pull_response_body( $response ) {
208 $http_response = $response['http_response'];
209 $response_object = $http_response->get_response_object();
210 return json_decode( $response_object->body );
211 }
212
213 /**
214 * @param mixed $response
215 * @return bool
216 */
217 private static function validate_response( $response ) {
218 return ! is_wp_error( $response ) && is_array( $response ) && isset( $response['http_response'] );
219 }
220
221 /**
222 * @return string
223 */
224 private static function get_url_to_connect_server() {
225 return 'https://api.strategy11.com/';
226 }
227
228 /**
229 * @return array
230 */
231 private static function build_headers_for_post() {
232 $password = self::maybe_get_pro_license();
233 if ( false === $password ) {
234 $password = 'lite_' . self::get_uuid();
235 }
236
237 $site_url = home_url();
238 $site_url = self::maybe_fix_wpml_url( $site_url );
239 // Remove protocol from url (our url cannot include the colon).
240 $site_url = preg_replace( '#^https?://#', '', $site_url );
241 // Remove port from url (mostly helpful in development).
242 $site_url = preg_replace( '/:[0-9]+/', '', $site_url );
243 $site_url = self::strip_lang_from_url( $site_url );
244
245 // $password is either a Pro license or a uuid (See FrmUsage::uuid).
246 return array(
247 'Authorization' => 'Basic ' . base64_encode( $site_url . ':' . $password ),
248 );
249 }
250
251 /**
252 * Get a unique ID to use for connecting Lite users.
253 *
254 * @return string
255 */
256 private static function get_uuid() {
257 $usage = new FrmUsage();
258 return $usage->uuid();
259 }
260
261 /**
262 * WPML might add a language to the url. Don't send that to the server.
263 */
264 private static function strip_lang_from_url( $url ) {
265 $split_on_language = explode( '/?lang=', $url );
266 if ( 2 === count( $split_on_language ) ) {
267 $url = $split_on_language[0];
268 }
269 return $url;
270 }
271
272 /**
273 * WPML alters the output of home_url.
274 * If it is active, use the WPML "absolute home" URL which is not modified.
275 *
276 * @param string $url
277 * @return string
278 */
279 private static function maybe_fix_wpml_url( $url ) {
280 if ( defined( 'ICL_SITEPRESS_VERSION' ) && ! ICL_PLUGIN_INACTIVE && class_exists( 'SitePress' ) ) {
281 global $wpml_url_converter;
282 $url = $wpml_url_converter->get_abs_home();
283 }
284 return $url;
285 }
286
287 /**
288 * Get a Pro license when Pro is active.
289 * Otherwise we'll use a uuid to support Lite.
290 *
291 * @return false|string
292 */
293 private static function maybe_get_pro_license() {
294 if ( FrmAppHelper::pro_is_installed() ) {
295 $pro_license = FrmAddonsController::get_pro_license();
296 if ( $pro_license ) {
297 $password = $pro_license;
298 }
299 }
300 return ! empty( $password ) ? $password : false;
301 }
302
303 /**
304 * Try to make sure the server time limit exceeds the request time limit.
305 *
306 * @param int $timeout seconds.
307 *
308 * @return void
309 */
310 private static function try_to_extend_server_timeout( $timeout ) {
311 if ( function_exists( 'set_time_limit' ) ) {
312 set_time_limit( $timeout + 10 );
313 }
314 }
315
316 /**
317 * @param string $mode either 'auto', 'live', or 'test'.
318 * @return string
319 */
320 private static function get_server_side_token_option_name( $mode = 'auto' ) {
321 return self::get_square_connect_option_name( 'server_password', $mode );
322 }
323
324 /**
325 * Generate a new client password for authenticating with Connect Service and save it locally as an option.
326 *
327 * @param string $mode 'live' or 'test'.
328 * @return string the client password.
329 */
330 private static function generate_client_password( $mode ) {
331 $client_password = wp_generate_password();
332 update_option( self::get_client_side_token_option_name( $mode ), $client_password, 'no' );
333 return $client_password;
334 }
335
336 /**
337 * @param string $mode either 'auto', 'live', or 'test'.
338 * @return string
339 */
340 private static function get_client_side_token_option_name( $mode = 'auto' ) {
341 return self::get_square_connect_option_name( 'client_password', $mode );
342 }
343
344 /**
345 * @return string
346 */
347 private static function get_mode_value() {
348 $settings = FrmSquareLiteAppHelper::get_settings();
349 return $settings->settings->test_mode ? 'test' : 'live';
350 }
351
352 /**
353 * @param string $mode
354 * @return bool|string
355 */
356 public static function get_merchant_id( $mode = 'auto' ) {
357 if ( 'auto' === $mode ) {
358 $mode = self::get_mode_value();
359 }
360 return get_option( self::get_merchant_id_option_name( $mode ) );
361 }
362
363 /**
364 * @param string $mode either 'auto', 'live', or 'test'.
365 * @return string
366 */
367 private static function get_merchant_id_option_name( $mode = 'auto' ) {
368 return self::get_square_connect_option_name( 'merchant_id', $mode );
369 }
370
371 /**
372 * @return string
373 */
374 private static function get_location_id_option_name( $mode = 'auto' ) {
375 return self::get_square_connect_option_name( 'merchant_location_id', $mode );
376 }
377
378 /**
379 * @return string
380 */
381 private static function get_merchant_currency_option_name( $mode = 'auto' ) {
382 return self::get_square_connect_option_name( 'merchant_currency', $mode );
383 }
384
385 /**
386 * @param string $key 'merchant_id', 'client_password', 'server_password'.
387 * @param string $mode either 'auto', 'live', or 'test'.
388 * @return string
389 */
390 private static function get_square_connect_option_name( $key, $mode = 'auto' ) {
391 return 'frm_square_connect_' . $key . self::get_active_mode_option_name_suffix( $mode );
392 }
393
394 /**
395 * @param string $mode either 'auto', 'live', or 'test'.
396 * @return string either _test or _live.
397 */
398 private static function get_active_mode_option_name_suffix( $mode = 'auto' ) {
399 if ( 'auto' !== $mode ) {
400 return '_' . $mode;
401 }
402 return '_' . FrmSquareLiteAppHelper::active_mode();
403 }
404
405 public static function check_for_redirects() {
406 if ( self::user_landed_on_the_oauth_return_url() ) {
407 self::redirect_oauth();
408 }
409 }
410
411 /**
412 * @return bool
413 */
414 private static function user_landed_on_the_oauth_return_url() {
415 return isset( $_GET['frm_square_api_return_oauth'] );
416 }
417
418 private static function redirect_oauth() {
419 $connected = self::check_server_for_oauth_merchant_id();
420 wp_safe_redirect( self::get_url_for_square_settings( $connected ) );
421 exit;
422 }
423
424 /**
425 * @param bool $connected
426 * @return string
427 */
428 private static function get_url_for_square_settings( $connected ) {
429 return admin_url( 'admin.php?page=formidable-settings&t=square_settings&connected=' . intval( $connected ) );
430 }
431
432 /**
433 * @return bool
434 */
435 private static function check_server_for_oauth_merchant_id() {
436 $mode = 'test' === FrmAppHelper::simple_get( 'mode' ) ? 'test' : 'live';
437
438 if ( self::get_merchant_id( $mode ) ) {
439 // Do not allow for initialize if there is already a configured merchant id.
440 return false;
441 }
442
443 $body = array(
444 'server_password' => get_option( self::get_server_side_token_option_name( $mode ) ),
445 'client_password' => get_option( self::get_client_side_token_option_name( $mode ) ),
446 'frm_square_api_mode' => $mode,
447 );
448 $data = self::post_to_connect_server( 'oauth_merchant_status', $body );
449
450 if ( is_object( $data ) && ! empty( $data->merchant_id ) ) {
451 update_option( self::get_merchant_id_option_name( $mode ), $data->merchant_id, 'no' );
452
453 $currency = self::get_merchant_currency( true );
454 $location_id = self::get_location_id( true );
455
456 if ( $currency ) {
457 update_option( self::get_merchant_currency_option_name( $mode ), $currency, 'no' );
458 }
459
460 if ( $location_id ) {
461 update_option( self::get_location_id_option_name( $mode ), $location_id, 'no' );
462 }
463
464 FrmTransLiteAppController::install();
465
466 return true;
467 }
468
469 return false;
470 }
471
472 public static function create_payment( $amount, $currency, $square_token, $verification_token, $description ) {
473 return self::post_with_authenticated_body(
474 'create_payment',
475 array(
476 'amount' => $amount,
477 'currency' => $currency,
478 'square_token' => $square_token,
479 'verification_token' => $verification_token,
480 'description' => $description,
481 )
482 );
483 }
484
485 /**
486 * @param string $action
487 * @param array $additional_body
488 *
489 * @return false|object
490 */
491 private static function post_with_authenticated_body( $action, $additional_body = array() ) {
492 $body = array_merge( self::get_standard_authenticated_body(), $additional_body );
493 $response = self::post_to_connect_server( $action, $body );
494 if ( is_object( $response ) ) {
495 return $response;
496 }
497 if ( is_array( $response ) ) {
498 // reformat empty arrays as empty objects
499 // if the response is an array, it's because it's empty. Everything with data is already an object.
500 return new stdClass();
501 }
502 if ( is_string( $response ) ) {
503 self::$latest_error_from_square_api = $response;
504 FrmTransLiteLog::log_message( 'Square API Error', $response );
505 } else {
506 self::$latest_error_from_square_api = '';
507 }
508 return false;
509 }
510
511 private static function get_standard_authenticated_body() {
512 $mode = self::get_mode_value_from_post();
513 return array(
514 'merchant_id' => get_option( self::get_merchant_id_option_name( $mode ) ),
515 'server_password' => get_option( self::get_server_side_token_option_name( $mode ) ),
516 'client_password' => get_option( self::get_client_side_token_option_name( $mode ) ),
517 );
518 }
519
520 /**
521 * Check $_POST for live or test mode value as it can be updated in real time from Stripe Settings and can be configured before the update is saved.
522 *
523 * @return string 'test' or 'live'
524 */
525 private static function get_mode_value_from_post() {
526 // phpcs:ignore WordPress.Security.NonceVerification.Missing
527 if ( empty( $_POST ) || ! array_key_exists( 'testMode', $_POST ) ) {
528 return FrmSquareLiteAppHelper::active_mode();
529 }
530 $test_mode = FrmAppHelper::get_param( 'testMode', '', 'post', 'absint' );
531 return $test_mode ? 'test' : 'live';
532 }
533
534 public static function get_latest_error_from_square_api() {
535 return self::$latest_error_from_square_api;
536 }
537
538 public static function refund_payment( $receipt_id ) {
539 return self::post_with_authenticated_body( 'refund_payment', array( 'receipt_id' => $receipt_id ) );
540 }
541
542 public static function create_subscription( $info ) {
543 return self::post_with_authenticated_body( 'create_subscription', compact( 'info' ) );
544 }
545
546 /**
547 * @param bool $force
548 * @return false|string
549 */
550 public static function get_location_id( $force = false ) {
551 if ( ! $force ) {
552 $location_id = get_option( self::get_location_id_option_name() );
553 if ( $location_id ) {
554 return $location_id;
555 }
556 }
557
558 $response = self::post_with_authenticated_body( 'get_location_id' );
559 if ( is_object( $response ) ) {
560 update_option( self::get_location_id_option_name(), $response->id, 'no' );
561 return $response->id;
562 }
563
564 return false;
565 }
566
567 /**
568 * @return array
569 */
570 public static function get_unprocessed_event_ids() {
571 $data = self::post_with_authenticated_body( 'get_unprocessed_event_ids' );
572 if ( false === $data || empty( $data->event_ids ) ) {
573 return array();
574 }
575 return $data->event_ids;
576 }
577
578 /**
579 * @param string $event_id
580 * @return false|object
581 */
582 public static function get_event( $event_id ) {
583 $event = wp_cache_get( $event_id, 'frm_square' );
584 if ( is_object( $event ) ) {
585 return $event;
586 }
587
588 $event = self::post_with_authenticated_body( 'get_event', compact( 'event_id' ) );
589
590 if ( false === $event || empty( $event->event ) ) {
591 return false;
592 }
593
594 wp_cache_set( $event_id, $event->event, 'frm_square' );
595
596 return $event->event;
597 }
598
599 /**
600 * @param string $event_id
601 * @return mixed
602 */
603 public static function process_event( $event_id ) {
604 return self::post_with_authenticated_body( 'process_event', compact( 'event_id' ) );
605 }
606
607 public static function get_payment( $payment_id ) {
608 return self::post_with_authenticated_body( 'get_payment', compact( 'payment_id' ) );
609 }
610
611 public static function get_subscription_id_for_payment( $payment_id ) {
612 return self::post_with_authenticated_body( 'get_subscription_id_for_payment', compact( 'payment_id' ) );
613 }
614
615 public static function cancel_subscription( $subscription_id ) {
616 return self::post_with_authenticated_body( 'cancel_subscription', compact( 'subscription_id' ) );
617 }
618
619 /**
620 * @return void
621 */
622 public static function handle_disconnect() {
623 self::disconnect();
624 self::reset_square_api_integration();
625 wp_send_json_success();
626 }
627
628 /**
629 * @return false|object
630 */
631 private static function disconnect() {
632 $additional_body = array(
633 'frm_square_api_mode' => self::get_mode_value_from_post(),
634 );
635 return self::post_with_authenticated_body( 'disconnect', $additional_body );
636 }
637
638 /**
639 * Delete every Square API option, calling when disconnecting.
640 *
641 * @return void
642 */
643 public static function reset_square_api_integration() {
644 $mode = self::get_mode_value_from_post();
645 delete_option( self::get_merchant_id_option_name( $mode ) );
646 delete_option( self::get_server_side_token_option_name( $mode ) );
647 delete_option( self::get_client_side_token_option_name( $mode ) );
648 delete_option( self::get_merchant_currency_option_name( $mode ) );
649 delete_option( self::get_location_id_option_name( $mode ) );
650 }
651
652 /**
653 * @param bool $force
654 * @return false|string
655 */
656 public static function get_merchant_currency( $force = false ) {
657 if ( ! $force ) {
658 $currency = get_option( self::get_merchant_currency_option_name() );
659 if ( $currency ) {
660 return $currency;
661 }
662 }
663
664 $response = self::post_with_authenticated_body( 'get_merchant_currency' );
665 if ( is_object( $response ) && ! empty( $response->currency ) ) {
666 update_option( self::get_merchant_currency_option_name(), $response->currency, 'no' );
667 return $response->currency;
668 }
669
670 return false;
671 }
672
673 /**
674 * @since 6.22
675 *
676 * @return bool
677 */
678 public static function at_least_one_mode_is_setup() {
679 return self::get_merchant_id( 'test' ) || self::get_merchant_id( 'live' );
680 }
681
682 /**
683 * Verify a site identifier is a match.
684 */
685 public static function verify() {
686 $option_name = 'frm_square_lite_last_verify_attempt';
687 $last_request = get_option( $option_name );
688
689 if ( $last_request && $last_request > strtotime( '-1 day' ) ) {
690 wp_send_json_error( 'Too many requests' );
691 }
692
693 $site_identifier = FrmAppHelper::get_post_param( 'site_identifier' );
694 $usage = new FrmUsage();
695 $uuid = $usage->uuid();
696
697 update_option( $option_name, time() );
698
699 if ( $site_identifier === $uuid ) {
700 wp_send_json_success();
701 }
702 wp_send_json_error();
703 }
704
705 public static function get_subscription( $subscription_id ) {
706 $response = self::post_with_authenticated_body( 'get_subscription', array( 'subscription_id' => $subscription_id ) );
707 if ( is_object( $response ) && is_object( $response->subscription ) ) {
708 return $response->subscription;
709 }
710 return false;
711 }
712 }
713