PluginProbe ʕ •ᴥ•ʔ
WooCommerce Square / 5.4.2
WooCommerce Square v5.4.2
5.4.2 5.4.1 5.4.0 trunk 1.0.25 1.0.26 1.0.27 1.0.28 1.0.29 1.0.30 1.0.31 1.0.32 1.0.33 1.0.34 1.0.35 1.0.36 1.0.37 1.0.38 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.2.5 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.6.0 2.7.0 2.8.0 2.9.0 2.9.1 3.0.0 3.0.1 3.0.2 3.0.3 3.1.0 3.2.0 3.3.0 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.7.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 4.0.0 4.1.0 4.2.0 4.2.1 4.2.2 4.2.3 4.3.0 4.3.1 4.3.2 4.4.0 4.4.1 4.4.2 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.6.2 4.6.3 4.6.4 4.7.0 4.7.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.1 4.8.2 4.8.3 4.8.4 4.8.5 4.8.6 4.8.7 4.8.8 4.9.0 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 4.9.6 4.9.7 4.9.8 4.9.9 5.0.0 5.0.1 5.1.0 5.1.1 5.1.2 5.2.0 5.3.0 5.3.1 5.3.2 5.3.3
woocommerce-square / includes / Handlers / Connection.php
woocommerce-square / includes / Handlers Last commit date
Product 1 month ago Async_Request.php 2 years ago Background_Job.php 2 months ago Category.php 1 month ago Connection.php 4 months ago Email.php 1 year ago Order.php 1 day ago Order_Sync.php 10 months ago Product.php 1 month ago Products.php 1 day ago Sync.php 3 years ago
Connection.php
548 lines
1 <?php
2 /**
3 * WooCommerce Square
4 *
5 * This source file is subject to the GNU General Public License v3.0
6 * that is bundled with this package in the file license.txt.
7 * It is also available through the world-wide-web at this URL:
8 * http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 or later
9 * If you did not receive a copy of the license and are unable to
10 * obtain it through the world-wide-web, please send an email
11 * to license@woocommerce.com so we can send you a copy immediately.
12 *
13 * DISCLAIMER
14 *
15 * Do not edit or add to this file if you wish to upgrade WooCommerce Square to newer
16 * versions in the future. If you wish to customize WooCommerce Square for your
17 * needs please refer to https://docs.woocommerce.com/document/woocommerce-square/
18 *
19 * @author WooCommerce
20 * @copyright Copyright: (c) 2019, Automattic, Inc.
21 * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License v3.0 or later
22 */
23
24 namespace WooCommerce\Square\Handlers;
25
26 use Square\Models\ListCustomersResponse;
27 use WooCommerce\Square;
28
29 defined( 'ABSPATH' ) || exit;
30
31 /**
32 * The admin connection handler.
33 *
34 * @since 2.0.0
35 */
36 class Connection {
37
38
39 /** @var string production connect URL */
40 const CONNECT_URL_PRODUCTION = 'https://api.woocommerce.com/integrations/login/square';
41
42 /** @var string sandbox connect URL */
43 const CONNECT_URL_SANDBOX = 'https://api.woocommerce.com/integrations/login/squaresandbox';
44
45 /** @var string production refresh URL */
46 const REFRESH_URL_PRODUCTION = 'https://api.woocommerce.com/integrations/renew/square';
47
48 /** @var string sandbox refresh URL */
49 const REFRESH_URL_SANDBOX = 'https://api.woocommerce.com/integrations/renew/squaresandbox';
50
51 /** @var Square\Plugin plugin instance */
52 protected $plugin;
53
54
55 /**
56 * Constructs the class.
57 *
58 * @since 2.0.0
59 *
60 * @param Square\Plugin $plugin plugin instance
61 */
62 public function __construct( Square\Plugin $plugin ) {
63
64 $this->plugin = $plugin;
65
66 $this->add_hooks();
67 }
68
69
70 /**
71 * Adds the action and filter hooks.
72 *
73 * @since 2.0.0
74 */
75 protected function add_hooks() {
76
77 add_action( 'admin_action_wc_square_connected', array( $this, 'handle_connected' ) );
78
79 add_action( 'admin_action_wc_square_disconnect', array( $this, 'handle_disconnect' ) );
80
81 // refresh the connection, triggered by Action Scheduler
82 add_action( 'wc_square_refresh_connection', array( $this, 'refresh_connection' ) );
83
84 // index customers, triggered by Action Scheduler
85 add_action( 'wc_square_index_customers', array( $this, 'index_customers' ) );
86 }
87
88
89 /**
90 * Handles a successful connection.
91 *
92 * @internal
93 *
94 * @since 2.0.0
95 */
96 public function handle_connected() {
97
98 // phpcs:ignore WordPress.Security.NonceVerification.Recommended - Setting variable, nonce checked next line.
99 $nonce = isset( $_GET['_wpnonce'] ) ? wc_clean( wp_unslash( $_GET['_wpnonce'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
100
101 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
102 $from = isset( $_GET['from'] ) ? wc_clean( wp_unslash( $_GET['from'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
103
104 // check the user role & nonce
105 if ( ! current_user_can( 'manage_woocommerce' ) || ! wp_verify_nonce( $nonce, 'wc_square_connected' ) ) {
106 wp_die( esc_html__( 'Sorry, you do not have permission to manage the Square connection.', 'woocommerce-square' ) );
107 }
108
109 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Input is sanitized before use, the call to urldecode() first triggers this warning
110 $access_token = ! empty( $_GET['square_access_token'] ) ? sanitize_text_field( wp_unslash( urldecode( $_GET['square_access_token'] ) ) ) : '';
111
112 if ( empty( $access_token ) ) {
113 $this->get_plugin()->log( 'Error: No access token was received.' );
114 add_action(
115 'admin_notices',
116 function () {
117 ?>
118 <div class="notice notice-error is-dismissible">
119 <p><?php esc_html_e( 'Square Error: We could not connect to Square. No access token was given.!', 'woocommerce-square' ); ?></p>
120 </div>
121 <?php
122 }
123 );
124 return;
125 }
126
127 $this->get_plugin()->get_settings_handler()->update_access_token( $access_token );
128 $this->get_plugin()->log( 'Access token successfully received.' );
129
130 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput -- Input is sanitized before use, the call to urldecode() first triggers this warning
131 $refresh_token = ! empty( $_GET['square_refresh_token'] ) ? sanitize_text_field( wp_unslash( urldecode( $_GET['square_refresh_token'] ) ) ) : '';
132 if ( empty( $refresh_token ) ) {
133 $this->get_plugin()->log( 'Failed to receive refresh token from connect server.' );
134 } else {
135 $this->get_plugin()->get_settings_handler()->update_refresh_token( $refresh_token );
136 $this->get_plugin()->log( 'Refresh token successfully received.' );
137 }
138
139 $this->schedule_refresh();
140 $this->schedule_customer_index();
141
142 // on connect after upgrading to v2.0 from v1.0, initiate a catalog sync to refresh the Square item IDs
143 if ( get_option( 'wc_square_updated_to_2_0_0' ) ) {
144
145 // delete any old access token from v1, as it will be invalidated
146 delete_option( 'woocommerce_square_merchant_access_token' );
147
148 if ( $this->get_plugin()->get_settings_handler()->is_system_of_record_square() ) {
149 $this->get_plugin()->get_sync_handler()->start_manual_sync();
150 }
151 }
152
153 delete_option( 'wc_square_updated_to_2_0_0' );
154
155 if ( wc_square()->get_settings_handler()->is_custom_square_auth_keys_set() ) {
156 update_option( 'wc_square_auth_key_updated', true );
157 }
158
159 wp_safe_redirect( 'wizard' === $from ? admin_url( 'admin.php?page=woocommerce-square-onboarding' ) : $this->get_plugin()->get_settings_url() );
160 exit;
161 }
162
163
164 /**
165 * Handles disconnection.
166 *
167 * @internal
168 *
169 * @since 2.0.0
170 */
171 public function handle_disconnect() {
172
173 // remove the refresh fail flag if previously set
174 delete_option( 'wc_square_refresh_failed' );
175
176 $nonce = isset( $_GET['_wpnonce'] ) ? wc_clean( wp_unslash( $_GET['_wpnonce'] ) ) : ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
177
178 // check the user role & nonce
179 if ( ! current_user_can( 'manage_woocommerce' ) || ! wp_verify_nonce( $nonce, 'wc_square_disconnect' ) ) {
180 wp_die( esc_html__( 'Sorry, you do not have permission to manage the Square connection.', 'woocommerce-square' ) );
181 }
182
183 // disconnect by clearing tokens, unscheduling syncs, etc...
184 $this->disconnect();
185
186 $this->get_plugin()->log( 'Manually disconnected' );
187
188 $this->get_plugin()->get_message_handler()->add_message( __( 'Disconnected successfully', 'woocommerce-square' ) );
189
190 wp_safe_redirect( $this->get_plugin()->get_settings_url() );
191 exit;
192 }
193
194
195 /**
196 * Disconnects the plugin.
197 *
198 * @since 2.0.0
199 */
200 public function disconnect() {
201
202 // don't try to refresh anymore
203 $this->unschedule_refresh();
204
205 // unschedule the interval sync
206 $this->get_plugin()->get_sync_handler()->unschedule_sync();
207
208 // fully clear the access token
209 $this->get_plugin()->get_settings_handler()->clear_access_tokens();
210 $this->get_plugin()->get_settings_handler()->clear_refresh_tokens();
211
212 // clear all background jobs so further API requests aren't attempted
213 $this->get_plugin()->get_background_job_handler()->clear_all_jobs();
214 }
215
216
217 /** Refresh methods ***********************************************************************************************/
218
219
220 /**
221 * Schedules the connection refresh.
222 *
223 * @since 2.0.0
224 */
225 public function schedule_refresh() {
226
227 if ( ! $this->get_plugin()->get_settings_handler()->is_connected() ) {
228 return;
229 }
230
231 /**
232 * Filters the frequency with which the OAuth connection should be refreshed.
233 *
234 * @since 2.0.0
235 *
236 * @param int $interval refresh interval
237 */
238 $interval = apply_filters( 'wc_square_connection_refresh_interval', WEEK_IN_SECONDS );
239
240 // Make sure that all refresh actions are cancelled before scheduling it.
241 $this->unschedule_refresh();
242
243 as_schedule_single_action( time() + $interval, 'wc_square_refresh_connection', array(), 'square' );
244 }
245
246
247 /**
248 * Refreshes the access token via the Woo proxy.
249 *
250 * @since 2.0.0
251 */
252 public function refresh_connection() {
253 if ( $this->get_plugin()->get_settings_handler()->is_sandbox() ) {
254 return;
255 }
256
257 try {
258
259 if ( $this->get_plugin()->get_settings_handler()->is_debug_enabled() ) {
260 $this->get_plugin()->log( 'Refreshing connection...' );
261 }
262
263 $refresh_token = $this->get_plugin()->get_settings_handler()->get_refresh_token();
264
265 if ( ! $refresh_token ) {
266 $this->get_plugin()->log( 'No refresh token stored, cannot refresh connection.' );
267 update_option( 'wc_square_refresh_failed', 'yes' );
268 wc_square()->get_email_handler()->get_access_token_email()->trigger();
269 return;
270 }
271
272 $request = array(
273 'body' => array(
274 'token' => $this->get_plugin()->get_settings_handler()->get_refresh_token(),
275 ),
276 'timeout' => 45,
277 );
278
279 // make the request
280 $response = wp_remote_post( $this->get_refresh_url(), $request );
281
282 // handle HTTP errors
283 if ( is_wp_error( $response ) ) {
284 throw new \Exception( $response->get_error_message() );
285 }
286
287 $response = new Square\API\Responses\Connection_Refresh_Response( wp_remote_retrieve_body( $response ) );
288
289 // check for errors in the response
290 if ( $response->has_error() ) {
291 throw new \Exception( $response->get_error_message() );
292 }
293
294 // ensure an access token, just in case
295 if ( ! $response->get_token() ) {
296 throw new \Exception( 'Access token missing from the response' );
297 }
298
299 // store the new token
300 $this->get_plugin()->get_settings_handler()->update_access_token( $response->get_token() );
301
302 // In case square updates the refresh token.
303 if ( $response->get_refresh_token() ) {
304 $this->get_plugin()->get_settings_handler()->update_refresh_token( $response->get_refresh_token() );
305 $this->get_plugin()->log( 'Connection successfully refreshed.' );
306 }
307
308 // in case this option was set
309 delete_option( 'wc_square_refresh_failed' );
310 } catch ( \Exception $exception ) {
311
312 $this->get_plugin()->log( 'Unable to refresh connection: ' . $exception->getMessage() );
313
314 update_option( 'wc_square_refresh_failed', 'yes' );
315
316 wc_square()->get_email_handler()->get_access_token_email()->trigger();
317 }
318
319 $this->schedule_refresh();
320 }
321
322
323 /**
324 * Unschedules the connection refresh.
325 *
326 * @since 2.0.0
327 */
328 protected function unschedule_refresh() {
329 as_unschedule_all_actions( 'wc_square_refresh_connection', array(), 'square' );
330 }
331
332
333 /** Customer index methods ****************************************************************************************/
334
335
336 /**
337 * Index existing Square customers.
338 *
339 * @since 2.0.0
340 *
341 * @param string $cursor pagination cursor
342 */
343 public function index_customers( $cursor = '' ) {
344
345 try {
346
347 $response = $this->get_plugin()->get_api()->get_customers( $cursor );
348
349 if ( $response->get_data() instanceof ListCustomersResponse && is_array( $response->get_data()->getCustomers() ) ) {
350
351 Square\Gateway\Customer_Helper::add_customers( $response->get_data()->getCustomers() );
352
353 // if there are more customers to query, schedule a followup action to index the next batch of customers
354 if ( $response->get_data()->getCursor() ) {
355 $this->schedule_customer_index( $response->get_data()->getCursor() );
356 }
357 }
358 } catch ( \Exception $exception ) {
359
360 }
361 }
362
363
364 /**
365 * Schedules the customer index action.
366 *
367 * @since 2.0.0
368 *
369 * @param string $cursor pagination cursor
370 */
371 protected function schedule_customer_index( $cursor = '' ) {
372
373 if ( false === as_next_scheduled_action( 'wc_square_index_customers', array( $cursor ), 'square' ) ) {
374 as_schedule_single_action( time(), 'wc_square_index_customers', array( $cursor ), 'square' );
375 }
376 }
377
378
379 /** Getter methods ************************************************************************************************/
380
381
382 /**
383 * Gets the Connect button HTML.
384 *
385 * @since 2.0.0
386 *
387 * @param bool $is_sandbox whether to point the button to production or sandbox
388 * @return string
389 */
390 public function get_connect_button_html( $is_sandbox = false ) {
391
392 ob_start();
393 ?>
394 <a href="<?php echo esc_url( $this->get_connect_url( $is_sandbox ) ); ?>" class="button-primary">
395 <?php esc_html_e( 'Connect with Square', 'woocommerce-square' ); ?>
396 </a>
397 <?php
398
399 return ob_get_clean();
400 }
401
402
403 /**
404 * Gets the disconnect button HTML.
405 *
406 * @since 2.0.0
407 *
408 * @return string
409 */
410 public function get_disconnect_button_html() {
411
412 ob_start();
413 ?>
414 <a href="<?php echo esc_url( $this->get_disconnect_url() ); ?>" class='button-primary'>
415 <?php echo esc_html__( 'Disconnect from Square', 'woocommerce-square' ); ?>
416 </a>
417 <?php
418
419 return ob_get_clean();
420 }
421
422
423 /**
424 * Gets the connection URL.
425 *
426 * @since 2.0.0
427 *
428 * @param bool $is_sandbox whether to point to production or sandbox
429 * @param array $args additional query args
430 *
431 * @return string
432 */
433 public function get_connect_url( $is_sandbox = false, $args = array() ) {
434
435 if ( $is_sandbox ) {
436 $raw_url = self::CONNECT_URL_SANDBOX;
437 } else {
438 $raw_url = self::CONNECT_URL_PRODUCTION;
439 }
440
441 /**
442 * Filters the connection URL.
443 *
444 * @since 2.0.0
445 *
446 * @param string $raw_url API URL
447 */
448 $url = (string) apply_filters( 'wc_square_api_url', $raw_url );
449
450 $action = 'wc_square_connected';
451 $redirect_url = wp_nonce_url( add_query_arg( 'action', $action, admin_url() ), $action );
452
453 // Append all args.
454 foreach ( $args as $key => $value ) {
455 $redirect_url = add_query_arg( $key, $value, $redirect_url );
456 }
457
458 $args = array(
459 'redirect' => rawurlencode( $redirect_url ),
460 'scopes' => implode( ',', $this->get_scopes() ),
461 );
462
463 return add_query_arg( $args, $url ); // nosemgrep:audit.php.wp.security.xss.query-arg -- This URL is escaped on output in get_connect_button_html().
464 }
465
466
467 /**
468 * Gets the disconnect URL.
469 *
470 * @since 2.0.0
471 *
472 * @return string
473 */
474 protected function get_disconnect_url() {
475
476 $action = 'wc_square_disconnect';
477 $url = add_query_arg( 'action', $action, admin_url() );
478
479 return wp_nonce_url( $url, $action );
480 }
481
482
483 /**
484 * Gets the token refresh URL.
485 *
486 * @since 2.0.0
487 *
488 * @return string
489 */
490 protected function get_refresh_url() {
491
492 return $this->get_plugin()->get_settings_handler()->is_sandbox() ? self::REFRESH_URL_SANDBOX : self::REFRESH_URL_PRODUCTION;
493 }
494
495 /**
496 * Gets the connection scopes.
497 *
498 * @since 2.0.0
499 *
500 * @return string[]
501 */
502 protected function get_scopes() {
503
504 $scopes = array(
505 'MERCHANT_PROFILE_READ',
506 'PAYMENTS_READ',
507 'PAYMENTS_WRITE',
508 'ORDERS_READ',
509 'ORDERS_WRITE',
510 'CUSTOMERS_READ',
511 'CUSTOMERS_WRITE',
512 'SETTLEMENTS_READ',
513 'ITEMS_READ',
514 'ITEMS_WRITE',
515 'INVENTORY_READ',
516 'INVENTORY_WRITE',
517 'GIFTCARDS_READ',
518 'GIFTCARDS_WRITE',
519 'PAYMENTS_WRITE',
520 'ORDERS_WRITE',
521 'DISCOUNT_CODES_READ',
522 'DISCOUNT_CODES_WRITE',
523 );
524
525 /**
526 * Hook to filter scopes.
527 *
528 * @since 2.0.0
529 */
530 return (array) apply_filters( 'wc_square_connection_scopes', $scopes );
531 }
532
533
534 /**
535 * Gets the plugin instance.
536 *
537 * @since 2.0.0
538 *
539 * @return Square\Plugin
540 */
541 public function get_plugin() {
542
543 return $this->plugin;
544 }
545
546
547 }
548