PluginProbe ʕ •ᴥ•ʔ
WooCommerce Square / 3.8.1
WooCommerce Square v3.8.1
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 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 / Products.php
woocommerce-square / includes / Handlers Last commit date
Product 3 years ago Background_Job.php 3 years ago Category.php 3 years ago Connection.php 3 years ago Email.php 3 years ago Order.php 3 years ago Product.php 3 years ago Products.php 3 years ago Sync.php 3 years ago
Products.php
821 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 defined( 'ABSPATH' ) || exit;
27 use WooCommerce\Square\Handlers\Product;
28 use WooCommerce\Square\Sync\Records;
29 use WooCommerce\Square;
30
31 /**
32 * Products admin handler.
33 *
34 * @since 2.0.0
35 */
36 class Products {
37
38
39 /** @var array associative array of product error codes and messages */
40 private $product_errors;
41
42 /** @var array associative array of memoized errors being output for a product at one time */
43 private $output_errors = array();
44
45 /** @var int[] array of product IDs that have been scheduled for sync in this request */
46 private $products_to_sync = array();
47
48 /** @var int[] array of product IDs that have been scheduled for deletion in this request */
49 private $products_to_delete = array();
50
51 /** @var Square\Plugin plugin instance */
52 private $plugin;
53
54 /**
55 * Sets up the products admin handler.
56 *
57 * @since 2.0.0
58 */
59 public function __construct( Square\Plugin $plugin ) {
60
61 $this->plugin = $plugin;
62
63 // add common errors
64 $this->product_errors = array(
65 /* translators: Placeholder: %s - product name */
66 'missing_sku' => __( "Please add an SKU to sync %s with Square. The SKU must match the item's SKU in your Square account.", 'woocommerce-square' ),
67 /* translators: Placeholder: %s - product name */
68 'missing_variation_sku' => __( "Please add an SKU to every variation of %s for syncing with Square. Each SKU must be unique and match the corresponding item's SKU in your Square account.", 'woocommerce-square' ),
69 );
70
71 // add hooks
72 $this->add_products_edit_screen_hooks();
73 $this->add_product_edit_screen_hooks();
74 $this->add_product_sync_hooks();
75 }
76
77 /**
78 * Adds hooks to the admin products edit screen.
79 *
80 * Products filtering, bulk actions, etc.
81 *
82 * @since 2.0.0
83 */
84 private function add_products_edit_screen_hooks() {
85
86 // adds an option to the "Filter by product type" dropdown
87 add_action( 'restrict_manage_posts', array( $this, 'add_filter_products_synced_with_square_option' ) );
88 // allow filtering products by sync status by altering results
89 add_filter( 'request', array( $this, 'filter_products_synced_with_square' ) );
90
91 // prevent copying Square data when duplicating a product automatically
92 add_action( 'woocommerce_product_duplicate', array( $this, 'handle_product_duplication' ), 20, 2 );
93
94 // handle quick/bulk edit actions in the products edit screen for setting sync status
95 add_action( 'woocommerce_product_quick_edit_end', array( $this, 'add_quick_edit_inputs' ) );
96 add_action( 'woocommerce_product_bulk_edit_end', array( $this, 'add_bulk_edit_inputs' ) );
97 add_action( 'woocommerce_product_quick_edit_save', array( $this, 'set_synced_with_square' ) );
98 add_action( 'woocommerce_product_bulk_edit_save', array( $this, 'set_synced_with_square' ) );
99
100 // export product sync status.
101 add_filter( 'woocommerce_product_export_column_names', array( $this, 'add_sync_status_to_column' ) );
102 add_filter( 'woocommerce_product_export_product_default_columns', array( $this, 'add_sync_status_to_column' ) );
103 add_filter( 'woocommerce_product_export_product_column_wc_square_synced', array( $this, 'export_sync_status_taxonomy' ), 10, 2 );
104 add_filter( 'woocommerce_csv_product_import_mapping_options', array( $this, 'register_sync_status_for_importer' ) );
105 add_filter( 'woocommerce_csv_product_import_mapping_default_columns', array( $this, 'map_sync_status_column' ) );
106 add_filter( 'woocommerce_product_import_pre_insert_product_object', array( $this, 'import_sync_status' ), 10, 2 );
107 }
108
109
110 /**
111 * Adds hooks to individual products edit screens.
112 *
113 * Product data input fields, variations, etc.
114 *
115 * @since 2.0.0
116 */
117 private function add_product_edit_screen_hooks() {
118
119 add_action( 'woocommerce_variation_options', array( $this, 'add_variation_manage_stock' ) );
120
121 // handle individual products input fields for setting sync status
122 add_action( 'woocommerce_product_options_general_product_data', array( $this, 'add_product_data_fields' ) );
123 add_action( 'woocommerce_admin_process_product_object', array( $this, 'process_product_data' ), 20 );
124 add_action( 'woocommerce_before_product_object_save', array( $this, 'maybe_adjust_square_stock' ) );
125
126 add_action( 'admin_notices', array( $this, 'add_notice_product_hidden_from_catalog' ) );
127 }
128
129
130 /**
131 * Adds our own hidden "manage stock" input to the variation fields.
132 *
133 * We disable the core checkbox, but this causes stock management to be disabled for the variations because the
134 * disabled field doesn't get POSTed. This overrides the checkbox value so that we can still disable it in the UI.
135 *
136 * @since 2.0.2
137 *
138 * @param int $loop currently looped variation
139 */
140 public function add_variation_manage_stock( $loop ) {
141
142 if ( ! wc_square()->get_settings_handler()->is_inventory_sync_enabled() ) {
143 return;
144 }
145
146 ?> <input type="hidden" id="wc_square_variation_manage_stock" name="variable_manage_stock[<?php echo esc_attr( $loop ); ?>]" value="1" />
147 <?php
148 }
149
150
151 /**
152 * Adds hooks to sync products that have been updated.
153 *
154 * @since 2.0.0
155 */
156 private function add_product_sync_hooks() {
157
158 add_action( 'woocommerce_update_product', array( $this, 'validate_product_update_and_sync' ) );
159 add_action( 'trashed_post', array( $this, 'maybe_stage_products_for_deletion' ) );
160 add_action( 'shutdown', array( $this, 'maybe_sync_staged_products' ) );
161 add_action( 'shutdown', array( $this, 'maybe_delete_staged_products' ) );
162 }
163
164 /**
165 * Adds an option to filter products by sync status.
166 *
167 * @internal
168 *
169 * @since 2.0.0
170 *
171 * @param string $post_type the post type context
172 */
173 public function add_filter_products_synced_with_square_option( $post_type ) {
174
175 if ( 'product' !== $post_type ) {
176 return;
177 }
178
179 $label = esc_html__( 'Synced with Square', 'woocommerce-square' );
180 // phpcs:ignore WordPress.Security.NonceVerification.Recommended - Nonce check not required, checked against known string, read-only action.
181 $selected = isset( $_GET['product_type'] ) && 'synced-with-square' === $_GET['product_type'] ? 'selected=\"selected\"' : '';
182
183 wc_enqueue_js(
184 "
185 jQuery( document ).ready( function( $ ) {
186 $( 'select#dropdown_product_type' ) . append( '<option value=\"synced-with-square\" ' + '" . $selected . "' + '>' + '" . $label . "' + '</option>' );
187 } );
188 "
189 );
190 }
191
192
193 /**
194 * Filters products in admin edit screen by sync status with Square.
195 *
196 * @internal
197 *
198 * @since 2.0.0
199 *
200 * @param array $query_vars query variables
201 * @return array
202 */
203 public function filter_products_synced_with_square( $query_vars ) {
204 global $typenow;
205
206 // phpcs:ignore WordPress.Security.NonceVerification.Recommended - Nonce check not required, just filtering products, read-only action.
207 if ( 'product' === $typenow && isset( $_GET['product_type'] ) && 'synced-with-square' === $_GET['product_type'] ) {
208
209 // not really a product type, otherwise WooCommerce will handle it as such
210 unset( $query_vars['product_type'] );
211
212 if ( ! isset( $query_vars['tax_query'] ) ) {
213 $query_vars['tax_query'] = array();
214 } else {
215 $query_vars['tax_query']['relation'] = 'AND';
216 }
217
218 $query_vars['tax_query'][] = array(
219 'taxonomy' => Product::SYNCED_WITH_SQUARE_TAXONOMY,
220 'field' => 'slug',
221 'terms' => array( 'yes' ),
222 );
223 }
224
225 return $query_vars;
226 }
227
228
229 /**
230 * Adds general product data options to a product metabox.
231 *
232 * @internal
233 *
234 * @since 2.0.0
235 */
236 public function add_product_data_fields() {
237 global $product_object;
238
239 if ( ! $product_object instanceof \WC_Product ) {
240 return;
241 }
242
243 // don't show fields if product sync is disabled
244 if ( ! wc_square()->get_settings_handler()->is_product_sync_enabled() ) {
245 return;
246 }
247
248 ?>
249 <div class="wc-square-sync-with-square options_group show_if_simple show_if_variable">
250 <?php
251
252 $selector = '_' . Product::SYNCED_WITH_SQUARE_TAXONOMY;
253 $value = Product::is_synced_with_square( $product_object ) ? 'yes' : 'no';
254 $errors = $this->check_product_sync_errors( $product_object );
255
256 $setting_label = wc_square()->get_settings_handler()->is_system_of_record_square() ? __( 'Update product data with Square data', 'woocommerce-square' ) : __( 'Send product data to Square', 'woocommerce-square' );
257
258 woocommerce_wp_checkbox(
259 array(
260 'id' => $selector,
261 'label' => __( 'Sync with Square', 'woocommerce-square' ),
262 'value' => $value,
263 'cbvalue' => 'yes',
264 'default' => 'no',
265 'description' => $setting_label,
266 'custom_attributes' => ! empty( $errors ) ? array( 'disabled' => 'disabled' ) : array(),
267 )
268 );
269
270 ?>
271 <p class="form-field wc-square-sync-with-square-errors">
272 <?php foreach ( $this->product_errors as $error_code => $error_message ) : ?>
273 <?php $styles = ! in_array( $error_code, array_keys( $errors ), true ) ? 'display:none; color:#A00;' : 'display:block; color:#A00;'; ?>
274 <span class="wc-square-sync-with-square-error <?php echo sanitize_html_class( $error_code ); ?>" style="<?php echo $styles; ?>"><?php echo $this->format_product_error( $error_code, $product_object ); ?></span>
275 <?php endforeach; ?>
276 </p>
277
278 <input type="hidden" id="<?php echo esc_attr( Product::SQUARE_VARIATION_ID_META_KEY ); ?>" value="<?php echo esc_attr( $product_object->get_meta( Product::SQUARE_VARIATION_ID_META_KEY ) ); ?>" />
279
280 </div>
281 <?php
282 }
283
284
285 /**
286 * Outputs HTML with a dropdown field to mark a product to be synced with Square.
287 *
288 * @since 2.0.0
289 *
290 * @param bool $bulk whether the field is meant for bulk edit
291 */
292 private function output_synced_with_square_edit_field( $bulk = false ) {
293
294 ?>
295 <div class="inline-edit-group wc-square-sync-with-square">
296 <label>
297 <span class="title"><?php esc_html_e( 'Sync with Square?', 'woocommerce-square' ); ?></span>
298 <span class="input-text-wrap">
299 <select class="square-synced" name="<?php echo esc_attr( Product::SYNCED_WITH_SQUARE_TAXONOMY ); ?>">
300 <?php if ( true === $bulk ) : // in bulk actions there's the option to leave the value unchanged (or unset) ?>
301 <option value="">&mdash; <?php esc_html_e( 'No change', 'woocommerce-square' ); ?> &mdash;</option>
302 <?php endif; ?>
303 <option value="no"><?php esc_html_e( 'No', 'woocommerce-square' ); ?></option>
304 <option value="yes"><?php esc_html_e( 'Yes', 'woocommerce-square' ); ?></option>
305 </select>
306 </span>
307 </label>
308 <p class="form-field wc-square-sync-with-square-errors">
309 <?php foreach ( $this->product_errors as $error_code => $error_message ) : ?>
310 <?php $product_name_placeholder = __( 'This product', 'woocommerce-square' ); ?>
311 <?php $product_name = strtolower( $product_name_placeholder ); ?>
312 <span class="wc-square-sync-with-square-error <?php echo $error_code; ?>" style="display:none; color:#A00;"><?php echo esc_html( sprintf( $error_message, $product_name ) ); ?></span>
313 <?php endforeach; ?>
314 </p>
315 </div>
316 <?php
317 }
318
319
320 /**
321 * Adds quick edit fields to the products screen.
322 *
323 * @internal
324 *
325 * @since 2.0.0
326 */
327 public function add_quick_edit_inputs() {
328
329 $this->output_synced_with_square_edit_field();
330 }
331
332
333 /**
334 * Adds bulk edit fields to the products screen.
335 *
336 * @internal
337 *
338 * @since 2.0.0
339 */
340 public function add_bulk_edit_inputs() {
341
342 $this->output_synced_with_square_edit_field( true );
343 }
344
345
346 /**
347 * In case Woo is the SOR, validates whether a product can be synced with Square and disable sync if not
348 *
349 * @since 2.0.8
350 *
351 * @param int $product_id the product ID
352 */
353 public function validate_product_update_and_sync( $product_id ) {
354 if ( ! wc_square()->get_settings_handler()->is_system_of_record_woocommerce() ) {
355 return;
356 }
357
358 $product = wc_get_product( $product_id );
359
360 if ( ! Product::is_synced_with_square( $product ) ) {
361 return;
362 }
363
364 $errors = $this->check_product_sync_errors( $product );
365
366 if ( ! empty( $errors ) ) {
367 // if there are errors, remove the link and display them
368 Product::unset_synced_with_square( $product );
369
370 foreach ( $errors as $error ) {
371 wc_square()->get_message_handler()->add_error( $error );
372 Records::set_record(
373 array(
374 'type' => 'alert',
375 'product_id' => $product_id,
376 'message' => $error,
377 )
378 );
379 }
380 } else {
381 $this->maybe_stage_product_for_sync( $product );
382 }
383 }
384
385 /**
386 * Stages a product for sync with Square on product save if Woo is the SOR and the product is set to 'synced with square'.
387 *
388 * @internal
389 *
390 * @since 2.0.0
391 *
392 * @param WC_Product $product the product object
393 */
394 public function maybe_stage_product_for_sync( $product ) {
395
396 if ( ! $product || ! Product::is_synced_with_square( $product ) || in_array( $product->get_id(), $this->products_to_sync, true ) ) {
397 return;
398 }
399
400 $in_progress = wc_square()->get_sync_handler()->get_job_in_progress();
401
402 if ( $in_progress ) {
403 // return early if an import that is updating existing products is in progress.
404 if ( isset( $in_progress->update_products_during_import ) && $in_progress->update_products_during_import ) {
405 return;
406 }
407
408 if ( in_array( $product->get_id(), $in_progress->product_ids, true ) ) {
409 return;
410 }
411 }
412
413 // the triggering action for this method can be called multiple times in a single request - keep track
414 // of product IDs that have been scheduled for sync here to avoid multiple syncs on the same request
415 $this->products_to_sync[] = $product->get_id();
416 }
417
418
419 /**
420 * Initializes a synchronization event for any staged products in this request.
421 *
422 * @internal
423 *
424 * @since 2.0.0
425 */
426 public function maybe_sync_staged_products() {
427
428 if ( ! defined( 'DOING_SQUARE_SYNC' ) && ! empty( $this->products_to_sync ) && wc_square()->get_settings_handler()->is_system_of_record_woocommerce() ) {
429
430 wc_square()->get_sync_handler()->start_manual_sync( $this->products_to_sync );
431 }
432 }
433
434
435 /**
436 * Removes a product from Square if it is deleted locally and Woo is the SOR.
437 *
438 * @since 2.0.0
439 *
440 * @param int $product_id the product ID
441 */
442 public function maybe_stage_products_for_deletion( $product_id ) {
443
444 if ( wc_square()->get_settings_handler()->is_system_of_record_woocommerce() ) {
445
446 $product = wc_get_product( $product_id );
447
448 if ( $product && Product::is_synced_with_square( $product ) ) {
449
450 // the triggering action for this method can be called multiple times in a single request - keep track
451 // of product IDs that have been scheduled for sync here to avoid multiple syncs on the same request
452 $this->products_to_delete[] = $product_id;
453 }
454 }
455 }
456
457
458 /**
459 * Deletes any products staged for remote deletion.
460 *
461 * @since 2.0.0
462 */
463 public function maybe_delete_staged_products() {
464
465 if ( ! empty( $this->products_to_delete ) && wc_square()->get_settings_handler()->is_system_of_record_woocommerce() ) {
466
467 wc_square()->get_sync_handler()->start_manual_deletion( $this->products_to_delete );
468 }
469 }
470
471
472 /**
473 * Sets a product's synced with Square status for quick/bulk edit action.
474 *
475 * @internal
476 *
477 * @since 2.0.0
478 *
479 * @param \WC_Product $product a product object
480 */
481 public function set_synced_with_square( $product ) {
482
483 $posted_data_key = Product::SYNCED_WITH_SQUARE_TAXONOMY;
484
485 if ( 'woocommerce_product_bulk_edit_save' === current_action() ) {
486 $default_value = null; // in bulk actions this will preserve the existing setting if nothing is specified
487 } else {
488 $default_value = 'no'; // in individual products context, the value should be always an explicit yes or no
489 }
490
491 $square_synced = isset( $_REQUEST[ $posted_data_key ] ) && in_array( $_REQUEST[ $posted_data_key ], array( 'yes', 'no' ), true ) ? $_REQUEST[ $posted_data_key ] : $default_value;
492
493 if ( is_string( $square_synced ) ) {
494 $errors = $this->check_product_sync_errors( $product );
495 if ( 'no' === $square_synced || empty( $errors ) ) {
496 Product::set_synced_with_square( $product, $square_synced );
497 } elseif ( ! empty( $errors ) ) {
498 foreach ( $errors as $error ) {
499 wc_square()->get_message_handler()->add_error( $error );
500 }
501 }
502 }
503 }
504
505
506 /**
507 * Updates Square sync status for a product upon saving.
508 *
509 * @internal
510 *
511 * @since 2.0.0
512 *
513 * @param \WC_Product $product product object
514 */
515 public function process_product_data( $product ) {
516
517 // don't process fields if product sync is disabled
518 if ( ! wc_square()->get_settings_handler()->is_product_sync_enabled() ) {
519 return;
520 }
521
522 // bail if no valid product found, if it's a variation, errors have already been output
523 if ( ! $product || ( $product instanceof \WC_Product_Variation || $product->is_type( 'product_variation' ) ) || ! empty( $this->output_errors[ $product->get_id() ] ) ) {
524 return;
525 }
526
527 $errors = array();
528 $posted_key = '_' . Product::SYNCED_WITH_SQUARE_TAXONOMY;
529 $set_synced = isset( $_POST[ $posted_key ] ) && 'yes' === $_POST[ $posted_key ];
530 $was_synced = Product::is_synced_with_square( $product );
531
532 // condition has unchanged
533 if ( ! $set_synced && ! $was_synced ) {
534 return;
535 }
536
537 if ( $set_synced || $was_synced ) {
538 if ( $set_synced && $product->is_type( 'variable' ) && wc_square()->get_settings_handler()->is_inventory_sync_enabled() ) {
539 // if syncing inventory with Square, parent variable products don't manage stock
540 $product->set_manage_stock( false );
541
542 // if there are no errors, and the product is variable, force enable stock management for all its variations
543 foreach ( $product->get_children() as $variation_id ) {
544 if ( $variation = wc_get_product( $variation_id ) ) {
545 $variation->set_manage_stock( true );
546 $variation->save();
547 }
548 }
549 }
550
551 // finally, set the product sync with Square flag
552 Product::set_synced_with_square( $product, $set_synced ? 'yes' : 'no' );
553 }
554 }
555
556
557 /**
558 * Adjusts a product's Square stock.
559 *
560 * @since 2.0.0
561 *
562 * @param \WC_Product $product product object
563 */
564 public function maybe_adjust_square_stock( $product ) {
565
566 // this is hooked in to general product object save, so scope to specifically saving products via the admin
567 if ( ! doing_action( 'wp_ajax_woocommerce_save_variations' ) && ! doing_action( 'woocommerce_admin_process_product_object' ) ) {
568 return;
569 }
570
571 // only send stock updates for Woo SOR
572 if ( ! wc_square()->get_settings_handler()->is_system_of_record_woocommerce() || ! wc_square()->get_settings_handler()->is_inventory_sync_enabled() ) {
573 return;
574 }
575
576 if ( ! $product instanceof \WC_Product || ! Product::is_synced_with_square( $product ) ) {
577 return;
578 }
579
580 $square_id = $product->get_meta( Product::SQUARE_VARIATION_ID_META_KEY );
581
582 // only send when the product has an associated Square ID
583 if ( ! $square_id ) {
584 return;
585 }
586
587 // set to manage stock if not a variable product
588 $product->set_manage_stock( ! $product->is_type( 'variable' ) );
589
590 $data = $product->get_data();
591 $changes = $product->get_changes();
592 $change = 0;
593
594 if ( isset( $data['stock_quantity'], $changes['stock_quantity'] ) ) {
595 $change = (int) $changes['stock_quantity'] - $data['stock_quantity'];
596 }
597
598 if ( $change !== 0 ) {
599
600 try {
601
602 if ( $change > 0 ) {
603 wc_square()->get_api()->add_inventory( $square_id, $change );
604 } else {
605 wc_square()->get_api()->remove_inventory( $square_id, $change );
606 }
607 } catch ( \Exception $exception ) {
608
609 wc_square()->log( 'Could not adjust Square inventory for ' . $product->get_formatted_name() . '. ' . $exception->getMessage() );
610
611 $quantity = (float) $data['stock_quantity'];
612
613 // if the API request fails, set the product quantity back from whence it came
614 $product->set_stock_quantity( $quantity );
615 }
616 }
617 }
618
619
620 /**
621 * Prevents copying Square data when duplicating a product in admin.
622 *
623 * @internal
624 *
625 * @since 2.0.0
626 *
627 * @param \WC_Product $duplicated_product product duplicate
628 * @param \WC_Product $original_product product duplicated
629 */
630 public function handle_product_duplication( $duplicated_product, $original_product ) {
631
632 if ( Product::is_synced_with_square( $original_product ) ) {
633 Product::unset_synced_with_square( $duplicated_product );
634 }
635
636 $duplicated_product->delete_meta_data( Product::SQUARE_ID_META_KEY );
637 $duplicated_product->delete_meta_data( Product::SQUARE_VARIATION_ID_META_KEY );
638
639 if ( $duplicated_product->is_type( 'variable' ) ) {
640
641 foreach ( $duplicated_product->get_children() as $duplicated_variation_id ) {
642
643 if ( $duplicated_product_variation = wc_get_product( $duplicated_variation_id ) ) {
644
645 $duplicated_product_variation->delete_meta_data( Product::SQUARE_VARIATION_ID_META_KEY );
646 $duplicated_product_variation->save_meta_data();
647 }
648 }
649 }
650
651 $duplicated_product->save_meta_data();
652 }
653
654
655 /**
656 * Outputs an admin notice when a product was hidden from catalog upon a sync error.
657 *
658 * @internal
659 *
660 * @since 2.0.0
661 */
662 public function add_notice_product_hidden_from_catalog() {
663 global $current_screen, $post;
664
665 if ( $post && $current_screen && 'product' === $current_screen->id ) {
666
667 $product = wc_get_product( $post );
668
669 if ( $product && 'hidden' === $product->get_catalog_visibility() ) {
670
671 $product_id = $product->get_id();
672 $records = Records::get_records( array( 'product' => $product_id ) );
673
674 foreach ( $records as $record ) {
675
676 if ( $record->was_product_hidden() && $product_id === $record->get_product_id() ) {
677
678 wc_square()->get_message_handler()->add_warning(
679 sprintf(
680 /* translators: Placeholder: %1$s - date (localized), %2$s - time (localized), %3$s - opening <a> HTML link tag, %4$s closing </a> HTML link tag */
681 esc_html__( 'The product catalog visibility has been set to "hidden", as a matching product could not be found in Square on %1$s at %2$s. %3$sCheck sync records%4$s.', 'woocommerce-square' ),
682 date_i18n( wc_date_format(), $record->get_timestamp() ),
683 date_i18n( wc_time_format(), $record->get_timestamp() ),
684 '<a href="' . esc_url( add_query_arg( array( 'section' => 'update' ), wc_square()->get_settings_url() ) ) . '">',
685 '</a>'
686 )
687 );
688
689 break;
690 }
691 }
692 }
693 }
694 }
695
696
697 /**
698 * Check whether this product can be synced with Square
699 *
700 * @param \WC_Product $product product object
701 * @return array errors
702 */
703 private function check_product_sync_errors( \WC_Product $product ) {
704
705 $errors = array();
706 if ( ! Product::has_sku( $product ) ) {
707 if ( $product->is_type( 'variable' ) ) {
708 $errors['missing_variation_sku'] = $this->format_product_error( 'missing_variation_sku', $product );
709 } else {
710 $errors['missing_sku'] = $this->format_product_error( 'missing_sku', $product );
711 }
712 }
713
714 return $errors;
715 }
716
717
718 /**
719 * Formats product error message with product information
720 *
721 * @param string $error error identifier (e.g. 'missing_variation_sku' or 'missing_sku')
722 * @param \WC_Product $product product object
723 * @return string formatted error message
724 */
725 private function format_product_error( string $error, \WC_Product $product ) {
726 return sprintf(
727 $this->product_errors[ $error ],
728 Product::get_product_edit_link( $product )
729 );
730 }
731
732
733 /**
734 * Gets the plugin instance.
735 *
736 * @since 2.0.8
737 *
738 * @return Plugin
739 */
740 protected function get_plugin() {
741 return $this->plugin;
742 }
743
744 /**
745 * Adds the sync status column to the export columns.
746 *
747 * @param array $columns Array of columns
748 * @return array
749 */
750 public function add_sync_status_to_column( $columns ) {
751 $columns['wc_square_synced'] = __( 'Sync with Square', 'woocommerce-square' );
752 return $columns;
753 }
754
755 /**
756 * Sets column data.
757 *
758 * @param mixed $value Value of a column
759 * @param \WC_Product $product WooCommerce product object.
760 */
761 public function export_sync_status_taxonomy( $value, $product ) {
762 $terms = get_terms(
763 array(
764 'object_ids' => $product->get_ID(),
765 'taxonomy' => 'wc_square_synced',
766 )
767 );
768
769 if ( ! is_wp_error( $terms ) && is_array( $terms ) && count( $terms ) > 0 ) {
770 $term = $terms[0];
771
772 return $term->name;
773 }
774
775 return 'no';
776 }
777
778 /**
779 * Registers the sync status to the importer.
780 *
781 * @param array $columns Array of columns
782 * @return array
783 */
784 public function register_sync_status_for_importer( $columns ) {
785 $columns['wc_square_synced'] = __( 'Sync with Square', 'woocommerce-square' );
786 return $columns;
787 }
788
789 /**
790 * Add automatic mapping support for wc_square_synced column.
791 *
792 * @param array $columns Array of columns
793 * @return array
794 */
795 public function map_sync_status_column() {
796 $columns[ __( 'Sync with Square', 'woocommerce-square' ) ] = 'wc_square_synced';
797 return $columns;
798 }
799
800 /**
801 * Imports square sync status.
802 *
803 * @param \WC_Product $product WooCommerce product.
804 * @param array Import data.
805 *
806 * @return array
807 */
808 public function import_sync_status( $product, $data ) {
809 if ( is_a( $product, 'WC_Product' ) ) {
810
811 if ( ! empty( $data['wc_square_synced'] ) && 'yes' === $data['wc_square_synced'] ) {
812 wp_set_object_terms( $product->get_id(), array( 'yes' ), 'wc_square_synced' );
813 } else {
814 wp_remove_object_terms( $product->get_id(), array( 'yes' ), 'wc_square_synced' );
815 }
816 }
817
818 return $product;
819 }
820 }
821