PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
merchant / inc / compatibility / class-merchant-ohio-theme.php

class-merchant-ohio-theme.php in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 2.3.2, at inc/compatibility/class-merchant-ohio-theme.php

718 lines 26.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Ohio Theme compatibility layer
4 *
5 * @package Merchant
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Ohio Theme compatibility layer
14 */
15 if ( ! class_exists( 'Merchant_Ohio_Theme' ) ) {
16 class Merchant_Ohio_Theme {
17
18 /**
19 * Constructor.
20 */
21 public function __construct() {
22 if ( ( is_admin() && ! wp_doing_ajax() ) || ! merchant_is_ohio_active() ) {
23 return;
24 }
25
26 // Custom CSS.
27 add_filter( 'merchant_custom_css', array( $this, 'frontend_custom_css' ) );
28
29 $ajax_cart = ! class_exists( 'OhioOptions' ) || OhioOptions::get( 'woocommerce_product_ajax_cart', true );
30
31 // Custom scripts
32 add_action( 'wp_footer', function() {
33 ?>
34 <script>
35 jQuery( document ).ready( function ( $ ) {
36
37 /**
38 * Ohio theme using their own AJAX scripts and not using 'added_to_cart' event which is required.
39 * So adding `added_to_cart` event here
40 */
41 const originalAjax = $.ajax;
42 $.ajax = function( options ) {
43 if (
44 options.url === wc_cart_fragments_params.ajax_url
45 && ( options.data.action === 'ohio_ajax_add_to_cart_woo' || options.data.action === 'ohio_ajax_add_to_cart_woo_single' ) ) {
46 const originalSuccess = options.success;
47 options.success = function(response) {
48 // Call the original Ohio success handler
49 originalSuccess.apply( this, arguments );
50 if ( response && ! response.error ) {
51 // Ensure fragments and cart_hash are present
52 const fragments = response.fragments || {};
53 const cart_hash = response.cart_hash || '';
54 const $button = $( '.data_button_ajax.loading' ); // Find the button that triggered it
55
56 // Trigger added_to_cart with the response data
57 $( document.body ).trigger( 'added_to_cart', [ fragments, cart_hash, $button ] );
58 }
59 };
60 }
61 return originalAjax.apply( this, arguments );
62 };
63
64 // Add loading class to show loading indicator
65 $( '.merchant-sticky-add-to-cart-wrapper button.button' ).addClass( 'data_button_ajax' );
66 $( 'body' ).on( 'adding_to_cart', function( event, $button, data ) {
67 if ( $button.closest( '.merchant-quick-view-content' ).length || $button.closest( '.merchant-sticky-add-to-cart-wrapper' ).length ) {
68 $button.addClass( 'btn-loading' );
69 }
70 } );
71
72 // Quick View, Wishlist, Product Labels, Product Video/Audio Position
73 $( '.merchant-product-labels, .merchant-quick-view-button, .merchant-wishlist-button, .merchant-product-video, .merchant-product-audio' ).each( function() {
74 const $product = $( this ).closest( 'li.product' );
75 const $thumbnail = $product.find( '.product-item-thumbnail' );
76
77 if ( $thumbnail.length ) {
78 $( this )
79 .appendTo( $thumbnail )
80 .css( { 'visibility': 'visible' } );
81 } else {
82 $( this )
83 .appendTo( $product )
84 .css( { 'visibility': 'visible' } );
85 }
86 } );
87 } );
88 </script>
89 <?php
90 } );
91
92 // Product labels
93 if ( Merchant_Modules::is_module_active( Merchant_Product_Labels::MODULE_ID ) ) {
94 $Product_Labels = Merchant_Modules::get_module( Merchant_Product_Labels::MODULE_ID );
95
96 // Archives/Loop
97 remove_action( 'woocommerce_before_shop_loop_item', array( $Product_Labels, 'loop_product_output' ) );
98 add_action( 'woocommerce_after_shop_loop_item_title', array( $Product_Labels, 'loop_product_output' ) );
99
100 // Single Product
101 remove_action( 'woocommerce_product_thumbnails', array( $Product_Labels, 'single_product_output' ) );
102 add_action( 'woocommerce_before_single_product_summary', array( $Product_Labels, 'single_product_output' ) );
103 }
104
105 // Quick View
106 if ( Merchant_Modules::is_module_active( Merchant_Quick_View::MODULE_ID ) && $ajax_cart ) {
107 $Quick_View = Merchant_Modules::get_module( Merchant_Quick_View::MODULE_ID );
108 remove_action( 'woocommerce_after_shop_loop_item', array( $Quick_View, 'quick_view_button' ) );
109 add_action( 'woocommerce_after_shop_loop_item_title', array( $Quick_View, 'quick_view_button' ) );
110 }
111
112 // Buy Now
113 if ( Merchant_Modules::is_module_active( Merchant_Buy_Now::MODULE_ID ) && $ajax_cart ) {
114 $Buy_Now = Merchant_Modules::get_module( Merchant_Buy_Now::MODULE_ID );
115 $button_renderer = $Buy_Now->get_button_renderer();
116
117 if ( $button_renderer ) {
118 remove_action( 'woocommerce_after_shop_loop_item', array( $button_renderer, 'shop_archive_button' ) );
119 add_action( 'woocommerce_after_shop_loop_item_title', array( $button_renderer, 'shop_archive_button' ) );
120 }
121 }
122
123 if ( merchant_is_pro_active() ) {
124 // FBT
125 if ( Merchant_Modules::is_module_active( Merchant_Frequently_Bought_Together::MODULE_ID ) ) {
126 add_filter( 'merchant_frequently_bought_together_woo_hook', function( $hook ) {
127 return 'woocommerce_product_after_tabs';
128 } );
129 }
130
131 // Recently Viewed Products
132 if ( Merchant_Modules::is_module_active( Merchant_Recently_Viewed_Products::MODULE_ID ) ) {
133 add_filter( 'merchant_recently_viewed_products_woo_hook', function( $hook ) {
134 return 'woocommerce_product_after_tabs';
135 } );
136
137 add_filter( 'merchant_recently_viewed_products_wrapper_classes', function( $classes ) {
138 $classes[] = 'woo-products';
139
140 return $classes;
141 } );
142 }
143
144 // Wishlist
145 if ( Merchant_Modules::is_module_active( Merchant_Wishlist::MODULE_ID ) ) {
146 add_filter( 'merchant_wishlist_archive_woo_hook', function( $hook ) {
147 return 'woocommerce_after_shop_loop_item_title';
148 } );
149 }
150
151 // Advanced Reviews
152 if ( Merchant_Modules::is_module_active( Merchant_Advanced_Reviews::MODULE_ID ) ) {
153 add_filter( 'merchant_advanced_review_woo_hook', function( $hook ) {
154 return 'woocommerce_product_after_tabs';
155 } );
156 }
157
158 // Side Cart
159 if ( Merchant_Modules::is_module_active( Merchant_Side_Cart::MODULE_ID ) ) {
160 // remove_filter( 'woocommerce_cart_item_name', 'ohio_add_cart_product_category', 99 );
161 }
162
163 // Product Video/Audio
164 if ( Merchant_Modules::is_module_active( Merchant_Product_Video::MODULE_ID ) || Merchant_Modules::is_module_active( Merchant_Product_Audio::MODULE_ID ) ) {
165 // Archive
166 add_filter( 'merchant_product_video_before_woo_hook', function() {
167 return 'woocommerce_after_shop_loop_item_title';
168 } );
169
170 add_filter( 'merchant_product_audio_before_woo_hook', function() {
171 return 'woocommerce_after_shop_loop_item_title';
172 } );
173
174 /*
175 * Single Product
176 *
177 * `YITH_Featured_Audio_Video_Init` function is needed in Ohio theme for firing `woocommerce_single_product_image_thumbnail_html` action
178 * That's why just defining it here to make sure it exists.
179 * No need any implementation
180 *
181 */
182 if ( ! function_exists( 'YITH_Featured_Audio_Video_Init' ) ) {
183 function YITH_Featured_Audio_Video_Init() {}
184 }
185 }
186
187 if ( Merchant_Modules::is_module_active( Merchant_Complementary_Products::MODULE_ID ) ) {
188 // Complementary products
189 add_action( 'woocommerce_ajax_added_to_cart', array( $this, 'complementary_products_support' ) );
190 }
191
192 // Size Chart
193 if ( Merchant_Modules::is_module_active( Merchant_Size_Chart::MODULE_ID ) ) {
194 add_filter( 'merchant_size_chart_skipped_placement_fires', array( $this, 'size_chart_skipped_placement_fires' ), 10, 3 );
195 }
196 }
197 }
198
199 /**
200 * Ignore Ohio's extra add to cart button hook on variable products.
201 *
202 * Ohio's variable.php fires woocommerce_before_add_to_cart_button above the
203 * variation block, then variation-add-to-cart-button.php fires it again where
204 * WooCommerce core does. Skipping the first one keeps the size chart next to
205 * the add to cart button, which is the position the setting offers.
206 *
207 * @param int $skipped Number of fires to ignore.
208 * @param string $hook_name The hook the size chart renders on.
209 * @param WC_Product $product The current product.
210 *
211 * @return int
212 */
213 public function size_chart_skipped_placement_fires( $skipped, $hook_name, $product ) {
214 if ( 'woocommerce_before_add_to_cart_button' !== $hook_name ) {
215 return $skipped;
216 }
217
218 if ( ! $product || ! $product->is_type( 'variable' ) ) {
219 return $skipped;
220 }
221
222 return 1;
223 }
224
225 /**
226 * Frontend custom CSS.
227 *
228 * @param string $css The custom CSS.
229 * @return string $css The custom CSS.
230 */
231 public function frontend_custom_css( $css ) {
232 $css .= Merchant_Side_Cart::get_module_custom_css();
233
234 $css .= '
235 body .cart_item .product-name .variation,
236 body .cart_item .product-total .variation {
237 display: block;
238 }
239 body .cart_item .product-name .variation dd,
240 body .cart_item .product-total .variation dd {
241 margin-inline: 0;
242 }
243 body .cart_item .product-name .variation p,
244 body .cart_item .product-total .variation p {
245 margin: 0;
246 }
247 body.wp-theme-ohio .merchant-cart-offers .merchant-cart-offers-container{
248 position: static;
249 }
250 ';
251
252 // Product labels
253 if ( Merchant_Modules::is_module_active( Merchant_Product_Labels::MODULE_ID ) ) {
254 $css .= '
255 .product-item {
256 position: relative;
257 }
258 li.product .merchant-product-labels {
259 visibility: hidden;
260 }
261 ';
262 }
263
264 // Size Chart
265 if ( Merchant_Modules::is_module_active( Merchant_Size_Chart::MODULE_ID ) ) {
266 // The add to cart button positions land inside a flex row here, where the
267 // button\'s own bottom margin knocks it out of line with the button.
268 $css .= '
269 .woo-product-details .variations_button .merchant-product-size-chart {
270 margin: 0 1rem 0 0;
271 }
272 ';
273 }
274
275 // Quick View
276 if ( Merchant_Modules::is_module_active( Merchant_Quick_View::MODULE_ID ) ) {
277 $css .= '
278 li.product .merchant-quick-view-button {
279 visibility: hidden;
280 }
281 ';
282 }
283
284 // Buy Now
285 if ( Merchant_Modules::is_module_active( Merchant_Buy_Now::MODULE_ID ) ) {
286 $css .= '
287 .data_button_ajax.btn-loading.loading:before {
288 position: absolute;
289 }
290 ';
291 }
292
293 if ( merchant_is_ohio_active() ) {
294 // Wishlist
295 if ( Merchant_Modules::is_module_active( Merchant_Wishlist::MODULE_ID ) ) {
296 $css .= '
297 li.product .merchant-wishlist-button {
298 visibility: hidden;
299 }
300 ';
301 }
302
303 // Side Cart
304 if ( Merchant_Modules::is_module_active( Merchant_Side_Cart::MODULE_ID ) ) {
305 $css .= '
306 .merchant-side-cart-item .woo-product-name,
307 .merchant-side-cart-item .woo-category {
308 display: none !important;
309 }
310 .merchant-side-cart-widget .product_list_widget .merchant-quantity-wrap span.merchant-cart-item-name a {
311 display: block !important;
312 }
313 ';
314 }
315
316 // Advanced Review
317 if ( Merchant_Modules::is_module_active( Merchant_Advanced_Reviews::MODULE_ID ) ) {
318 $css .= '
319 .merchant-adv-reviews-modal-photo-slider .star-rating:before,
320 .merchant-adv-reviews .star-rating:before {
321 content: "�
322
323
324
325
326 " !important;
327 }
328 ';
329 }
330
331 // Product Video/Audio
332 if ( Merchant_Modules::is_module_active( Merchant_Product_Video::MODULE_ID ) || Merchant_Modules::is_module_active( Merchant_Product_Audio::MODULE_ID ) ) {
333 $css .= '
334 li.product:has(.merchant-product-video) .product-item-thumbnail .image-holder,
335 li.product:has(.merchant-product-audio) .product-item-thumbnail .image-holder {
336 display: none;
337 }
338 li.product .merchant-product-video,
339 li.product .merchant-product-audio {
340 visibility: hidden;
341 }
342 ';
343 }
344 }
345
346 // Delivery & Pickup
347 if ( Merchant_Modules::is_module_active( Merchant_Delivery_Pickup::MODULE_ID ) ) {
348 // Ohio pins `.woo-sidebar tr td:first-child` to 52% and dequeues WooCommerce's
349 // CSS, the table margin included.
350 $css .= '
351 body .woo-sidebar table.merchant-dp-summary tr th:first-child,
352 body .woo-sidebar table.merchant-dp-summary tr td:first-child {
353 width: auto;
354 }
355
356 body .woocommerce-order-details table.merchant-dp-summary {
357 margin-top: 1.5rem;
358 }
359 ';
360 }
361
362 return $css;
363 }
364
365 /**
366 * Get the variation ID from attributes for a variable product.
367 *
368 * @param int $product_id The variable product ID.
369 * @param array $variation_data The selected variation attributes.
370 *
371 * @return int The variation ID or 0 if not found.
372 */
373 private function get_variation_id_from_attributes( $product_id, $variation_data ) {
374 // Get the product object.
375 $product = wc_get_product( $product_id );
376 if ( ! $product || $product->get_type() !== 'variable' ) {
377 return 0;
378 }
379
380 // Ensure variation data keys are prefixed correctly.
381 $formatted_variation_data = array();
382 foreach ( $variation_data as $key => $value ) {
383 // If the key doesn't start with 'attribute_', assume it's the attribute name and prefix it.
384 $formatted_key = strpos( $key, 'attribute_' ) === 0 ? $key : 'attribute_' . sanitize_title( $key );
385 $formatted_variation_data[ $formatted_key ] = sanitize_text_field( $value );
386 }
387
388 // Get available variations for comparison (optional debugging).
389 $available_variations = $product->get_available_variations();
390 $data_store = WC_Data_Store::load( 'product' );
391 $variation_id = $data_store->find_matching_product_variation( $product, $formatted_variation_data );
392
393 /**
394 * Filter the variation ID found for a complementary product.
395 *
396 * @param int $variation_id The found variation ID (0 if not found).
397 * @param int $product_id The variable product ID.
398 * @param array $formatted_variation_data The formatted variation attributes.
399 * @param array $available_variations The available variations for the product.
400 *
401 * @since 2.2.1
402 */
403 return apply_filters( 'merchant_ohio_complementary_products_variation_id',
404 (int) $variation_id,
405 $product_id,
406 $formatted_variation_data,
407 $available_variations
408 );
409 }
410
411 /**
412 * Add support for complementary products module with Ohio theme custom add to cart action.
413 *
414 * @param int $cart_product_id The ID of the product added to cart.
415 *
416 * @return void
417 * @throws Exception When the cart fails to add the product.
418 */
419 public function complementary_products_support( $cart_product_id ) {
420 if ( ! $this->should_handle_complementary_products() ) {
421 return;
422 }
423
424 $request = $this->get_complementary_request_data();
425 if ( ! $request ) {
426 return;
427 }
428
429 $cart = WC()->cart;
430 $cart_item = $this->find_parent_cart_item( $cart, $cart_product_id, $request['variation_id'] );
431
432 if ( ! $cart_item ) {
433 return;
434 }
435
436 $this->mark_parent_cart_item(
437 $cart,
438 $cart_item['key'],
439 $cart_item['data'],
440 $request['campaign_id']
441 );
442
443 $this->add_complementary_products_to_cart(
444 $cart,
445 $cart_product_id,
446 $cart_item['key'],
447 $request['campaign_id'],
448 $request['products'],
449 $request['quantity']
450 );
451 }
452
453 /**
454 * Check if complementary products logic should run.
455 *
456 * @return bool
457 */
458 private function should_handle_complementary_products() {
459 if ( ! Merchant_Modules::is_module_active( Merchant_Complementary_Products::MODULE_ID ) ) {
460 return false;
461 }
462
463 if ( ! merchant_is_ohio_active() ) {
464 return false;
465 }
466
467 if ( empty( $_POST['complementary_offer_products'] ) || '[]' === $_POST['complementary_offer_products'] ) {
468 return false;
469 }
470
471 if (
472 ! isset( $_POST['complementary_offer_products_nonce'] )
473 || ! wp_verify_nonce(
474 sanitize_text_field( wp_unslash( $_POST['complementary_offer_products_nonce'] ) ),
475 'complementary_offer_products'
476 )
477 ) {
478 return false;
479 }
480
481 return true;
482 }
483
484 /**
485 * Read and sanitize complementary products request data.
486 *
487 * We are ignoring nonce verification here because it is already verified in should_handle_complementary_products().
488 *
489 * @return array|null
490 */
491 private function get_complementary_request_data() {
492 if ( ! isset( $_POST['complementary_offer_products'] ) ) { //phpcs:ignore WordPress.Security.NonceVerification.Missing
493 return null;
494 }
495 //phpcs:ignore WordPress.Security.NonceVerification.Missing
496 $complementary_products_json = sanitize_text_field( wp_unslash( $_POST['complementary_offer_products'] ) );
497 $complementary_products = json_decode( $complementary_products_json, true );
498
499 if ( ! is_array( $complementary_products ) || empty( $complementary_products ) ) {
500 return null;
501 }
502
503 //phpcs:ignore WordPress.Security.NonceVerification.Missing
504 if ( ! isset( $_POST['complementary_offer_id'] ) ) {
505 return null;
506 }
507
508 //phpcs:ignore WordPress.Security.NonceVerification.Missing
509 $quantity = isset( $_POST['quantity'] ) ? absint( wp_unslash( $_POST['quantity'] ) ) : 1;
510
511 //phpcs:ignore WordPress.Security.NonceVerification.Missing
512 $variation_id = isset( $_POST['variation_id'] ) ? absint( wp_unslash( $_POST['variation_id'] ) ) : 0;
513
514 return array(
515 'products' => $complementary_products,
516 //phpcs:ignore WordPress.Security.NonceVerification.Missing
517 'campaign_id' => sanitize_text_field( wp_unslash( $_POST['complementary_offer_id'] ) ),
518 'quantity' => $quantity,
519 'variation_id' => $variation_id,
520 );
521 }
522
523 /**
524 * Find the parent cart item (main product) in the cart.
525 *
526 * @param WC_Cart $cart Cart instance.
527 * @param int $product_id Product ID added to cart.
528 * @param int $variation_id Variation ID (if any).
529 *
530 * @return array|null {
531 * @type string $key
532 * @type array $data
533 * }
534 */
535 private function find_parent_cart_item( WC_Cart $cart, $product_id, $variation_id ) {
536 $cart_items = $cart->get_cart();
537
538 foreach ( $cart_items as $key => $cart_item ) {
539 if ( $variation_id && isset( $cart_item['variation_id'] ) && (int) $cart_item['variation_id'] === (int) $variation_id ) {
540 return array(
541 'key' => $key,
542 'data' => $cart_item,
543 );
544 }
545
546 if ( ! $variation_id && isset( $cart_item['product_id'] ) && (int) $cart_item['product_id'] === (int) $product_id ) {
547 return array(
548 'key' => $key,
549 'data' => $cart_item,
550 );
551 }
552 }
553
554 return null;
555 }
556
557 /**
558 * Mark the parent cart item with complementary metadata.
559 *
560 * @param WC_Cart $cart Cart instance.
561 * @param string $cart_item_key Cart item key.
562 * @param array $cart_item_data Existing cart item data.
563 * @param string $campaign_id Campaign/offer ID.
564 *
565 * @return void
566 */
567 private function mark_parent_cart_item( WC_Cart $cart, $cart_item_key, array $cart_item_data, $campaign_id ) {
568 $cart_item_data['is_complementary_parent'] = true;
569 $cart_item_data['merchant_added_via'] = 'complementary_products';
570 $cart_item_data['merchant_module_id'] = Merchant_Complementary_Products::MODULE_ID;
571 $cart_item_data['complementary_offer_id'] = $campaign_id;
572
573 $cart->cart_contents[ $cart_item_key ] = array_merge( $cart->cart_contents[ $cart_item_key ], $cart_item_data );
574 }
575
576 /**
577 * Add all complementary products to the cart.
578 *
579 * @param WC_Cart $cart
580 * @param int $main_product_id
581 * @param string $parent_cart_item_key
582 * @param string $campaign_id
583 * @param array $complementary_products
584 * @param int $quantity
585 *
586 * @return void
587 */
588 private function add_complementary_products_to_cart(
589 WC_Cart $cart,
590 $main_product_id,
591 $parent_cart_item_key,
592 $campaign_id,
593 $complementary_products,
594 $quantity
595 ) {
596 foreach ( $complementary_products as $comp_product ) {
597 $this->add_single_complementary_product(
598 $cart,
599 $main_product_id,
600 $parent_cart_item_key,
601 $campaign_id,
602 $comp_product,
603 $quantity
604 );
605 }
606 }
607
608 /**
609 * Add a single complementary product to the cart.
610 *
611 * @param WC_Cart $cart
612 * @param int $main_product_id
613 * @param string $parent_cart_item_key
614 * @param string $campaign_id
615 * @param array $comp_product
616 * @param int $quantity
617 *
618 * @return void
619 */
620 private function add_single_complementary_product(
621 WC_Cart $cart,
622 $main_product_id,
623 $parent_cart_item_key,
624 $campaign_id,
625 array $comp_product,
626 $quantity
627 ) {
628 $comp_product_id = absint( $comp_product['product_id'] );
629 $comp_product_object = wc_get_product( $comp_product_id );
630
631 if ( ! $comp_product_object ) {
632 wc_add_notice(
633 sprintf(
634 /* translators: %s: Product ID */
635 __( 'Complementary product with ID "%s" does not exist.', 'merchant' ),
636 $comp_product_id
637 ),
638 'error'
639 );
640
641 return;
642 }
643
644 $product_type = sanitize_text_field( $comp_product['product_type'] );
645 $variation_data = $this->sanitize_variation_data(
646 isset( $comp_product['variation_data'] ) ? $comp_product['variation_data'] : array()
647 );
648
649 $comp_variation_id = 0;
650 if ( 'variable' === $product_type && ! empty( $variation_data ) ) {
651 $comp_variation_id = $this->get_variation_id_from_attributes( $comp_product_id, $variation_data );
652 if ( ! $comp_variation_id ) {
653 wc_add_notice(
654 sprintf(
655 /* translators: %s: Product name */
656 __( 'Invalid variation selected for complementary product "%s".', 'merchant' ),
657 $comp_product_object->get_name()
658 ),
659 'error'
660 );
661
662 return;
663 }
664 }
665
666 $comp_cart_item_data = array(
667 'complementary_main_product' => $main_product_id,
668 'complementary_parent' => $parent_cart_item_key,
669 'complementary_offer_id' => $campaign_id,
670 'merchant_added_via' => 'complementary_products',
671 'merchant_module_id' => Merchant_Complementary_Products::MODULE_ID,
672 'is_complementary' => true,
673 );
674
675 $added_to_cart = $cart->add_to_cart(
676 $comp_product_id,
677 $quantity,
678 $comp_variation_id,
679 $variation_data,
680 $comp_cart_item_data
681 );
682
683 if ( ! $added_to_cart ) {
684 wc_add_notice(
685 sprintf(
686 /* translators: %s: Product name */
687 __( 'Failed to add complementary product "%s" to the cart.', 'merchant' ),
688 $comp_product_object->get_name()
689 ),
690 'error'
691 );
692 }
693 }
694
695 /**
696 * Sanitize variation data array.
697 *
698 * @param array $variation_data
699 *
700 * @return array
701 */
702 private function sanitize_variation_data( array $variation_data ) {
703 $sanitized = array();
704
705 foreach ( $variation_data as $key => $value ) {
706 $sanitized_key = sanitize_key( $key );
707 $sanitized_value = map_deep( $value, 'sanitize_text_field' );
708 $sanitized[ $sanitized_key ] = $sanitized_value;
709 }
710
711 return $sanitized;
712 }
713 }
714
715 add_action( 'init', function() {
716 new Merchant_Ohio_Theme();
717 } );
718 }