PluginProbe
WooCommerce Square / 4.4.0
WooCommerce Square v4.4.0
5.5.0 5.4.3 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 All 132 releases
woocommerce-square / includes / Handlers / Connection.php
Connection.php
537 lines 14.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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://connect.woocommerce.com/login/square';
41
42 /** @var string sandbox connect URL */
43 const CONNECT_URL_SANDBOX = 'https://connect.woocommerce.com/login/squaresandbox';
44
45 /** @var string production refresh URL */
46 const REFRESH_URL_PRODUCTION = 'https://connect.woocommerce.com/renew/square';
47
48 /** @var string sandbox refresh URL */
49 const REFRESH_URL_SANDBOX = 'https://connect.woocommerce.com/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'] ) ) : '';
100
101 // check the user role & nonce
102 if ( ! current_user_can( 'manage_woocommerce' ) || ! wp_verify_nonce( $nonce, 'wc_square_connected' ) ) {
103 wp_die( esc_html__( 'Sorry, you do not have permission to manage the Square connection.', 'woocommerce-square' ) );
104 }
105
106 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - Input is santiized before use, the call to urldecode() first triggers this warning
107 $access_token = ! empty( $_GET['square_access_token'] ) ? sanitize_text_field( urldecode( $_GET['square_access_token'] ) ) : '';
108
109 if ( empty( $access_token ) ) {
110 $this->get_plugin()->log( 'Error: No access token was received.' );
111 add_action(
112 'admin_notices',
113 function () {
114 ?>
115 <div class="notice notice-error is-dismissible">
116 <p><?php esc_html_e( 'Square Error: We could not connect to Square. No access token was given.!', 'woocommerce-square' ); ?></p>
117 </div>
118 <?php
119 }
120 );
121 return;
122 }
123
124 $this->get_plugin()->get_settings_handler()->update_access_token( $access_token );
125 $this->get_plugin()->log( 'Access token successfully received.' );
126
127 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized - Input is santiized before use, the call to urldecode() first triggers this warning
128 $refresh_token = ! empty( $_GET['square_refresh_token'] ) ? sanitize_text_field( urldecode( $_GET['square_refresh_token'] ) ) : '';
129 if ( empty( $refresh_token ) ) {
130 $this->get_plugin()->log( 'Failed to receive refresh token from connect server.' );
131 } else {
132 $this->get_plugin()->get_settings_handler()->update_refresh_token( $refresh_token );
133 $this->get_plugin()->log( 'Refresh token successfully received.' );
134 }
135
136 $this->schedule_refresh();
137 $this->schedule_customer_index();
138
139 // on connect after upgrading to v2.0 from v1.0, initiate a catalog sync to refresh the Square item IDs
140 if ( get_option( 'wc_square_updated_to_2_0_0' ) ) {
141
142 // delete any old access token from v1, as it will be invalidated
143 delete_option( 'woocommerce_square_merchant_access_token' );
144
145 if ( $this->get_plugin()->get_settings_handler()->is_system_of_record_square() ) {
146 $this->get_plugin()->get_sync_handler()->start_manual_sync();
147 }
148 }
149
150 delete_option( 'wc_square_updated_to_2_0_0' );
151
152 if ( wc_square()->get_settings_handler()->is_custom_square_auth_keys_set() ) {
153 update_option( 'wc_square_auth_key_updated', true );
154 }
155
156 wp_safe_redirect( $this->get_plugin()->get_settings_url() );
157 exit;
158 }
159
160
161 /**
162 * Handles disconnection.
163 *
164 * @internal
165 *
166 * @since 2.0.0
167 */
168 public function handle_disconnect() {
169
170 // remove the refresh fail flag if previously set
171 delete_option( 'wc_square_refresh_failed' );
172
173 // phpcs:ignore WordPress.Security.NonceVerification.Recommended - Setting var, nonce checked next line.
174 $nonce = isset( $_GET['_wpnonce'] ) ? wc_clean( $_GET['_wpnonce'] ) : '';
175
176 // check the user role & nonce
177 if ( ! current_user_can( 'manage_woocommerce' ) || ! wp_verify_nonce( $nonce, 'wc_square_disconnect' ) ) {
178 wp_die( esc_html__( 'Sorry, you do not have permission to manage the Square connection.', 'woocommerce-square' ) );
179 }
180
181 // disconnect by clearing tokens, unscheduling syncs, etc...
182 $this->disconnect();
183
184 $this->get_plugin()->log( 'Manually disconnected' );
185
186 $this->get_plugin()->get_message_handler()->add_message( __( 'Disconnected successfully', 'woocommerce-square' ) );
187
188 wp_safe_redirect( $this->get_plugin()->get_settings_url() );
189 exit;
190 }
191
192
193 /**
194 * Disconnects the plugin.
195 *
196 * @since 2.0.0
197 */
198 public function disconnect() {
199
200 // don't try to refresh anymore
201 $this->unschedule_refresh();
202
203 // unschedule the interval sync
204 $this->get_plugin()->get_sync_handler()->unschedule_sync();
205
206 // fully clear the access token
207 $this->get_plugin()->get_settings_handler()->clear_access_tokens();
208 $this->get_plugin()->get_settings_handler()->clear_refresh_tokens();
209
210 // clear all background jobs so further API requests aren't attempted
211 $this->get_plugin()->get_background_job_handler()->clear_all_jobs();
212 }
213
214
215 /** Refresh methods ***********************************************************************************************/
216
217
218 /**
219 * Schedules the connection refresh.
220 *
221 * @since 2.0.0
222 */
223 public function schedule_refresh() {
224
225 if ( ! $this->get_plugin()->get_settings_handler()->is_connected() ) {
226 return;
227 }
228
229 /**
230 * Filters the frequency with which the OAuth connection should be refreshed.
231 *
232 * @since 2.0.0
233 *
234 * @param int $interval refresh interval
235 */
236 $interval = apply_filters( 'wc_square_connection_refresh_interval', WEEK_IN_SECONDS );
237
238 // Make sure that all refresh actions are cancelled before scheduling it.
239 $this->unschedule_refresh();
240
241 as_schedule_single_action( time() + $interval, 'wc_square_refresh_connection', array(), 'square' );
242 }
243
244
245 /**
246 * Refreshes the access token via the Woo proxy.
247 *
248 * @since 2.0.0
249 */
250 public function refresh_connection() {
251 if ( $this->get_plugin()->get_settings_handler()->is_sandbox() ) {
252 return;
253 }
254
255 try {
256
257 if ( $this->get_plugin()->get_settings_handler()->is_debug_enabled() ) {
258 $this->get_plugin()->log( 'Refreshing connection...' );
259 }
260
261 $refresh_token = $this->get_plugin()->get_settings_handler()->get_refresh_token();
262
263 if ( ! $refresh_token ) {
264 $this->get_plugin()->log( 'No refresh token stored, cannot refresh connection.' );
265 update_option( 'wc_square_refresh_failed', 'yes' );
266 wc_square()->get_email_handler()->get_access_token_email()->trigger();
267 return;
268 }
269
270 $request = array(
271 'body' => array(
272 'token' => $this->get_plugin()->get_settings_handler()->get_refresh_token(),
273 ),
274 'timeout' => 45,
275 );
276
277 // make the request
278 $response = wp_remote_post( $this->get_refresh_url(), $request );
279
280 // handle HTTP errors
281 if ( is_wp_error( $response ) ) {
282 throw new \Exception( $response->get_error_message() );
283 }
284
285 $response = new Square\API\Responses\Connection_Refresh_Response( wp_remote_retrieve_body( $response ) );
286
287 // check for errors in the response
288 if ( $response->has_error() ) {
289 throw new \Exception( $response->get_error_message() );
290 }
291
292 // ensure an access token, just in case
293 if ( ! $response->get_token() ) {
294 throw new \Exception( 'Access token missing from the response' );
295 }
296
297 // store the new token
298 $this->get_plugin()->get_settings_handler()->update_access_token( $response->get_token() );
299
300 // In case square updates the refresh token.
301 if ( $response->get_refresh_token() ) {
302 $this->get_plugin()->get_settings_handler()->update_refresh_token( $response->get_refresh_token() );
303 $this->get_plugin()->log( 'Connection successfully refreshed.' );
304 }
305
306 // in case this option was set
307 delete_option( 'wc_square_refresh_failed' );
308 } catch ( \Exception $exception ) {
309
310 $this->get_plugin()->log( 'Unable to refresh connection: ' . $exception->getMessage() );
311
312 update_option( 'wc_square_refresh_failed', 'yes' );
313
314 wc_square()->get_email_handler()->get_access_token_email()->trigger();
315 }
316
317 $this->schedule_refresh();
318 }
319
320
321 /**
322 * Unschedules the connection refresh.
323 *
324 * @since 2.0.0
325 */
326 protected function unschedule_refresh() {
327 as_unschedule_all_actions( 'wc_square_refresh_connection', array(), 'square' );
328 }
329
330
331 /** Customer index methods ****************************************************************************************/
332
333
334 /**
335 * Index existing Square customers.
336 *
337 * @since 2.0.0
338 *
339 * @param string $cursor pagination cursor
340 */
341 public function index_customers( $cursor = '' ) {
342
343 try {
344
345 $response = $this->get_plugin()->get_api()->get_customers( $cursor );
346
347 if ( $response->get_data() instanceof ListCustomersResponse && is_array( $response->get_data()->getCustomers() ) ) {
348
349 Square\Gateway\Customer_Helper::add_customers( $response->get_data()->getCustomers() );
350
351 // if there are more customers to query, schedule a followup action to index the next batch of customers
352 if ( $response->get_data()->getCursor() ) {
353 $this->schedule_customer_index( $response->get_data()->getCursor() );
354 }
355 }
356 } catch ( \Exception $exception ) {
357
358 }
359 }
360
361
362 /**
363 * Schedules the customer index action.
364 *
365 * @since 2.0.0
366 *
367 * @param string $cursor pagination cursor
368 */
369 protected function schedule_customer_index( $cursor = '' ) {
370
371 if ( false === as_next_scheduled_action( 'wc_square_index_customers', array( $cursor ), 'square' ) ) {
372 as_schedule_single_action( time(), 'wc_square_index_customers', array( $cursor ), 'square' );
373 }
374 }
375
376
377 /** Getter methods ************************************************************************************************/
378
379
380 /**
381 * Gets the Connect button HTML.
382 *
383 * @since 2.0.0
384 *
385 * @param bool $is_sandbox whether to point the button to production or sandbox
386 * @return string
387 */
388 public function get_connect_button_html( $is_sandbox = false ) {
389
390 ob_start();
391 ?>
392 <a href="<?php echo esc_url( $this->get_connect_url( $is_sandbox ) ); ?>" class="button-primary">
393 <?php esc_html_e( 'Connect with Square', 'woocommerce-square' ); ?>
394 </a>
395 <?php
396
397 return ob_get_clean();
398 }
399
400
401 /**
402 * Gets the disconnect button HTML.
403 *
404 * @since 2.0.0
405 *
406 * @return string
407 */
408 public function get_disconnect_button_html() {
409
410 ob_start();
411 ?>
412 <a href="<?php echo esc_url( $this->get_disconnect_url() ); ?>" class='button-primary'>
413 <?php echo esc_html__( 'Disconnect from Square', 'woocommerce-square' ); ?>
414 </a>
415 <?php
416
417 return ob_get_clean();
418 }
419
420
421 /**
422 * Gets the connection URL.
423 *
424 * @since 2.0.0
425 *
426 * @param bool $is_sandbox whether to point to production or sandbox
427 * @return string
428 */
429 public function get_connect_url( $is_sandbox = false ) {
430
431 if ( $is_sandbox ) {
432 $raw_url = self::CONNECT_URL_SANDBOX;
433 } else {
434 $raw_url = self::CONNECT_URL_PRODUCTION;
435 }
436
437 /**
438 * Filters the connection URL.
439 *
440 * @since 2.0.0
441 *
442 * @param string $raw_url API URL
443 */
444 $url = (string) apply_filters( 'wc_square_api_url', $raw_url );
445
446 $action = 'wc_square_connected';
447 $redirect_url = wp_nonce_url( add_query_arg( 'action', $action, admin_url() ), $action );
448
449 $args = array(
450 'redirect' => urlencode( urlencode( $redirect_url ) ),
451 'scopes' => implode( ',', $this->get_scopes() ),
452 );
453
454 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().
455 }
456
457
458 /**
459 * Gets the disconnect URL.
460 *
461 * @since 2.0.0
462 *
463 * @return string
464 */
465 protected function get_disconnect_url() {
466
467 $action = 'wc_square_disconnect';
468 $url = add_query_arg( 'action', $action, admin_url() );
469
470 return wp_nonce_url( $url, $action );
471 }
472
473
474 /**
475 * Gets the token refresh URL.
476 *
477 * @since 2.0.0
478 *
479 * @return string
480 */
481 protected function get_refresh_url() {
482
483 return $this->get_plugin()->get_settings_handler()->is_sandbox() ? self::REFRESH_URL_SANDBOX : self::REFRESH_URL_PRODUCTION;
484 }
485
486 /**
487 * Gets the connection scopes.
488 *
489 * @since 2.0.0
490 *
491 * @return string[]
492 */
493 protected function get_scopes() {
494
495 $scopes = array(
496 'MERCHANT_PROFILE_READ',
497 'PAYMENTS_READ',
498 'PAYMENTS_WRITE',
499 'ORDERS_READ',
500 'ORDERS_WRITE',
501 'CUSTOMERS_READ',
502 'CUSTOMERS_WRITE',
503 'SETTLEMENTS_READ',
504 'ITEMS_READ',
505 'ITEMS_WRITE',
506 'INVENTORY_READ',
507 'INVENTORY_WRITE',
508 'GIFTCARDS_READ',
509 'GIFTCARDS_WRITE',
510 'PAYMENTS_WRITE',
511 'ORDERS_WRITE',
512 );
513
514 /**
515 * Hook to filter scopes.
516 *
517 * @since 2.0.0
518 */
519 return (array) apply_filters( 'wc_square_connection_scopes', $scopes );
520 }
521
522
523 /**
524 * Gets the plugin instance.
525 *
526 * @since 2.0.0
527 *
528 * @return Square\Plugin
529 */
530 public function get_plugin() {
531
532 return $this->plugin;
533 }
534
535
536 }
537