PluginProbe
Orderable – Restaurant & Food Ordering System / 1.16.0
Orderable – Restaurant & Food Ordering System v1.16.0
0.1.4 0.1.5 0.2.0 1.0.0 1.1.0 1.1.1 1.10.0 1.10.1 1.11.0 1.12.0 1.12.1 1.12.2 1.13.0 1.14.0 1.15.0 1.16.0 1.17.0 1.17.1 1.18.0 1.19.0 1.19.1 1.19.2 1.2.0 1.20.0 1.20.1 All 44 releases
orderable / inc / class-products.php

class-products.php in Orderable – Restaurant & Food Ordering System 1.16.0, at inc/class-products.php

1,073 lines 31.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Product methods.
4 *
5 * @package Orderable/Classes
6 */
7
8 defined( 'ABSPATH' ) || exit;
9
10 /**
11 * Products class.
12 */
13 class Orderable_Products {
14 /**
15 * Init
16 */
17 public static function run() {
18 add_filter( 'woocommerce_format_price_range', array( __CLASS__, 'format_price_range' ), 10, 3 );
19 add_filter( 'woocommerce_product_is_visible', array( __CLASS__, 'set_product_visibility' ), 10, 2 );
20 add_action( 'template_redirect', array( __CLASS__, 'products_404' ) );
21 add_filter( 'woocommerce_cart_item_permalink', array( __CLASS__, 'disable_cart_link' ), 10, 3 );
22 add_filter( 'woocommerce_product_query_tax_query', array( __CLASS__, 'remove_hidden_categories_from_products_query' ), 10, 2 );
23 add_filter( 'get_terms_args', array( __CLASS__, 'remove_hidden_categories_from_terms_query' ), 10, 2 );
24 add_filter( 'wp_sitemaps_posts_query_args', array( __CLASS__, 'remove_hidden_products_from_sitemap' ), 10, 2 );
25 add_filter( 'wp_sitemaps_taxonomies_query_args', array( __CLASS__, 'remove_hidden_categories_from_sitemap' ), 10, 2 );
26 add_filter( 'orderable_add_to_cart_button_args', array( __CLASS__, 'update_button_args_to_allow_add_to_cart_without_side_drawer' ), 10, 3 );
27 add_filter( 'woocommerce_add_to_cart_fragments', array( __CLASS__, 'handle_adding_product_without_side_drawer' ) );
28 add_filter( 'orderable_main_class', array( __CLASS__, 'add_quantity_roller_class' ), 10, 2 );
29
30 add_action( 'woocommerce_cart_item_removed', array( __CLASS__, 'update_product_counter_fragments_for_removed_item' ), 10, 2 );
31 }
32
33 /**
34 * Format price range.
35 *
36 * @param string $price
37 * @param float $from
38 * @param float $to
39 *
40 * @return string
41 */
42 public static function format_price_range( $price, $from, $to ) {
43 return sprintf( '%s: %s', __( 'From', 'orderable' ), wc_price( $from ) );
44 }
45
46 /**
47 * Get products, sorted by category.
48 *
49 * @param array $args
50 *
51 * @return array
52 */
53 public static function get_products_by_category( $args = array() ) {
54 // Disable this filter as we don't want to disable categories during our own calls to them.
55 remove_filter( 'get_terms_args', array( __CLASS__, 'remove_hidden_categories_from_terms_query' ), 10 );
56
57 $categories = ! empty( $args['categories'] ) ? $args['categories'] : array();
58 $categories = is_string( $categories ) ? array_filter( explode( ',', $categories ) ) : $categories;
59 $has_categories = ! empty( $categories );
60
61 $categories = self::order_categories_by_menu_order( $categories );
62
63 $orderby = ! defined( 'ORDERABLE_PRO_VERSION' ) || empty( $_GET['order_by'] ) ? '' : sanitize_text_field( wp_unslash( $_GET['order_by'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
64
65 if ( empty( $orderby ) ) {
66 $orderby = ! defined( 'ORDERABLE_PRO_VERSION' ) || empty( $args['sort'] ) ? 'menu_order' : $args['sort'];
67 }
68
69 $products = array();
70
71 /**
72 * Filter description.
73 *
74 * @since 1.0.0
75 * @hook orderable_flatten_products_by_category_level
76 * @param string $flatten_level The flatten level. Default: `all`.
77 * @param array $args The layout args.
78 * @return string New value
79 */
80 $flatten_level = apply_filters( 'orderable_flatten_products_by_category_level', 'all', $args );
81
82 if ( 'all' === $flatten_level ) {
83 $products[] = array(
84 'category' => array(),
85 'products' => array(),
86 );
87
88 $categories_slug = array_filter(
89 array_map(
90 function( $category_id ) {
91 $category = get_term_by( 'id', $category_id, 'product_cat' );
92
93 return empty( $category->slug ) ? false : $category->slug;
94 },
95 $categories
96 )
97 );
98
99 $products[0]['products'] = self::get_products(
100 array(
101 'category' => $categories_slug,
102 'orderby' => $orderby,
103 'limit' => 500,
104 )
105 );
106
107 /**
108 * Filter the products sorted by categories to be shown in the product layout.
109 *
110 * @since 1.0.0
111 * @hook orderable_get_products_by_category
112 * @param array $products The products.
113 * @param array $args The args to retrieve the products.
114 * @param string $flatten_level The flatten level e.g. `all` and `children`.
115 * @return array New value
116 */
117 return apply_filters( 'orderable_get_products_by_category', $products, $args, $flatten_level );
118 }
119
120 if ( $has_categories ) {
121 foreach ( $categories as $category_id ) {
122 $category = get_term( $category_id, 'product_cat' );
123
124 if ( is_wp_error( $category ) || empty( $category ) ) {
125 continue;
126 }
127
128 $products[ $category_id ] = array(
129 'category' => array(
130 'name' => $category->name,
131 'slug' => $category->slug,
132 'description' => $category->description,
133 'depth' => 0,
134 'children' => null,
135 ),
136 'products' => array(),
137 );
138
139 $children_categories = get_terms(
140 array(
141 'taxonomy' => 'product_cat',
142 'orderby' => 'menu_order',
143 'hide_empty' => true,
144 'parent' => $category->term_id,
145 )
146 );
147
148 if ( ! empty( $children_categories ) ) {
149 $products[ $category_id ]['category']['children'] = array();
150
151 foreach ( $children_categories as $child_category ) {
152 $category_products = self::get_products(
153 array(
154 'limit' => 500,
155 'category' => array( $child_category->slug ),
156 'orderby' => $orderby,
157 )
158 );
159
160 if ( empty( $category_products ) ) {
161 continue;
162 }
163
164 if ( 'children' === $flatten_level ) {
165 $products_in_category = empty( $products[ $category_id ]['products'] ) ? array() : $products[ $category_id ]['products'];
166 $products[ $category_id ]['products'] = array_merge( $products_in_category, $category_products );
167
168 continue;
169 }
170
171 $products[ $category_id ]['category']['children'][ $child_category->term_id ] = array(
172 'category' => array(
173 'name' => $child_category->name,
174 'description' => $child_category->description,
175 'depth' => 1,
176 'parent' => $category_id,
177 ),
178 'products' => $category_products,
179 );
180 }
181 } else {
182 $category_products = self::get_products(
183 array(
184 'limit' => 500,
185 'category' => array( $category->slug ),
186 'orderby' => $orderby,
187 )
188 );
189
190 if ( ! empty( $category_products ) ) {
191 $products[ $category_id ]['products'] = $category_products;
192
193 // Add parent attribute if parent is a root category.
194 if ( in_array( $category->parent, $categories, true ) ) {
195 $products[ $category_id ]['category']['parent'] = $category->parent;
196 }
197 }
198 }
199 }
200 } else {
201 $category_products = self::get_products(
202 array(
203 'limit' => 500,
204 'orderby' => $orderby,
205 )
206 );
207
208 if ( ! empty( $category_products ) ) {
209 $products[] = array(
210 'category' => null,
211 'products' => $category_products,
212 );
213 }
214 }
215
216 // Remove categories with no products.
217 foreach ( $products as $key => $product_collection ) {
218 if ( ! empty( $product_collection['products'] ) || ! empty( $product_collection['category']['children'] ) ) {
219 continue;
220 }
221
222 unset( $products[ $key ] );
223 }
224
225 // Turn this back on to re-disable hidden categories.
226 add_filter( 'get_terms_args', array( __CLASS__, 'remove_hidden_categories_from_terms_query' ), 10, 2 );
227
228 $products = self::maybe_flatten_products_by_category( array_filter( $products ), $args );
229
230 // phpcs:ignore WooCommerce.Commenting.CommentHooks
231 return apply_filters( 'orderable_get_products_by_category', $products, $args, $flatten_level );
232 }
233
234 /**
235 * Get Products.
236 *
237 * A wrapper around the WooCommerce `wc_get_products` and `wc_products_array_orderby` functions.
238 *
239 * @param array $args Arguments.
240 *
241 * @return array
242 */
243 public static function get_products( $args ) {
244 $args['status'] = 'publish'; // Ensure only published products are returned.
245
246 if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
247 $args['stock_status'] = 'instock';
248 }
249
250 /**
251 * Filter arguments used to retrieve products from database
252 *
253 * @param array $args WC_Product_Query arguments
254 *
255 * @return array New query arguments
256 * @since 1.2.1
257 * @hook orderable_get_products_args
258 */
259 $products = wc_get_products( apply_filters( 'orderable_get_products_args', $args ) );
260
261 if ( ! empty( $products ) ) {
262 $orderby = empty( $args['orderby'] ) ? 'menu_order' : $args['orderby'];
263
264 $order = 'price-desc' === $orderby ? 'desc' : 'asc';
265 $orderby = 'price-desc' === $orderby ? 'price' : $orderby;
266
267 $products = wc_products_array_orderby( $products, $orderby, $order );
268 }
269
270 return apply_filters( 'orderable_get_products', $products, $args );
271 }
272
273 /**
274 * Order Categories by menu order
275 *
276 * @param array $categories Categories.
277 *
278 * @return array
279 */
280 public static function order_categories_by_menu_order( $categories ) {
281 $categories_ordered = array();
282 $category_terms = get_terms(
283 array(
284 'taxonomy' => 'product_cat',
285 'orderby' => 'menu_order',
286 'hide_empty' => true,
287 )
288 );
289
290 foreach ( $category_terms as $term ) {
291 if ( ! in_array( $term->term_id, $categories, true ) ) {
292 continue;
293 }
294
295 $categories_ordered[] = $term->term_id;
296 }
297
298 return $categories_ordered;
299 }
300
301 /**
302 * Maybe flatten products by category.
303 *
304 * @param array $products
305 * @param array $args
306 *
307 * @return array
308 */
309 public static function maybe_flatten_products_by_category( $products, $args = array() ) {
310 // If we're already listing all products with no categories, escape.
311 if ( empty( $products ) || isset( $products[0] ) ) {
312 return $products;
313 }
314
315 $flatten_level = apply_filters( 'orderable_flatten_products_by_category_level', 'all', $args );
316
317 // Don't flatten if set to "none".
318 if ( 'none' === $flatten_level ) {
319 return $products;
320 }
321
322 $flattened_products = array();
323
324 if ( 'children' === $flatten_level ) {
325 $orderby = empty( $args['sort'] ) ? 'menu_order' : $args['sort'];
326
327 $order = 'price-desc' === $orderby ? 'desc' : 'asc';
328 $orderby = 'price-desc' === $orderby ? 'price' : $orderby;
329
330 foreach ( $products as $category_id => $product_group ) {
331 $product_group['products'] = wc_products_array_orderby( $product_group['products'], $orderby, $order );
332
333 $flattened_products[ $category_id ] = $product_group;
334 }
335 } else {
336 $flattened_products[] = array(
337 'category' => null,
338 'products' => array(),
339 );
340
341 foreach ( $products as $product_group ) {
342 if ( ! empty( $product_group['products'] ) ) {
343 // Add category products to list.
344 $flattened_products[0]['products'] = array_merge( $flattened_products[0]['products'], $product_group['products'] );
345
346 continue;
347 } elseif ( ! empty( $product_group['category']['children'] ) ) {
348 foreach ( $product_group['category']['children'] as $child_product_group ) {
349 // Add category products to list.
350 $flattened_products[0]['products'] = array_merge( $flattened_products[0]['products'], $child_product_group['products'] );
351
352 continue;
353 }
354 }
355 }
356 }
357
358 return $flattened_products;
359 }
360
361 /**
362 * Get add to cart button.
363 *
364 * @param WC_Product $product Product.
365 * @param string $classes Button classes.
366 * @param array $layout_settings The product layout settings.
367 *
368 * @return string
369 */
370 public static function get_add_to_cart_button( $product, $classes = '', $layout_settings = array() ) {
371 global $orderable_single_product;
372
373 $args = array(
374 'trigger' => self::get_add_to_cart_trigger( $product ),
375 'product_id' => $product->get_id(),
376 'product_type' => $product->get_type(),
377 'variation_id' => null,
378 'variation_attributes' => array(),
379 'text' => __( 'Add', 'orderable' ),
380 'classes' => $classes,
381 );
382
383 if ( Orderable_Helpers::is_variable_product( $args['product_type'] ) ) {
384 $args['trigger'] = 'product-options';
385 $args['text'] = empty( $orderable_single_product ) ? __( 'Select', 'orderable' ) : $args['text'];
386 } elseif ( 'variation' === $args['product_type'] ) {
387 $args['product_id'] = $product->get_parent_id();
388 $args['variation_id'] = $product->get_id();
389 }
390
391 if ( ! $product->is_in_stock() ) {
392 $args['classes'] .= ' orderable-button--out-of-stock';
393 $args['text'] = __( 'Out of Stock', 'orderable' );
394 }
395
396 if ( Orderable_Helpers::is_product_in_the_cart( $product->get_id() ) ) {
397 $args['classes'] .= ' orderable-button--product-in-the-cart';
398 }
399
400 $product_quantity = Orderable_Helpers::get_product_quantity_in_the_cart( $product->get_id() );
401
402 /**
403 * Filter the Add to Cart button args.
404 *
405 * @since 1.0.0
406 * @hook orderable_add_to_cart_button_args
407 * @param array $args The button args.
408 * @param WC_Product $product The product.
409 * @param array $layout_settings The product layout settings.
410 * @return array New value
411 */
412 $args = apply_filters( 'orderable_add_to_cart_button_args', $args, $product, $layout_settings );
413
414 $counter_element_classes = 'orderable-product__actions-counter';
415
416 return sprintf(
417 '<button
418 class="orderable-button %1$s"
419 data-orderable-trigger="%2$s"
420 data-orderable-product-id="%3$d"
421 data-orderable-product-type="%4$s"
422 data-orderable-variation-id="%5$d"
423 data-orderable-variation-attributes=""
424 data-quantity="1"
425 data-product_id="%3$d"
426 data-product_sku="%6$s"
427 data-product_name="%7$s"
428 data-price="%8$s"
429 >
430 %9$s
431 <span class="%10$s" data-orderable-product-quantity="%11$d">%11$d</span>
432 </button>',
433 esc_attr( $args['classes'] ),
434 esc_attr( $args['trigger'] ),
435 esc_attr( $args['product_id'] ),
436 esc_attr( $args['product_type'] ),
437 esc_attr( $args['variation_id'] ),
438 esc_attr( $product->get_sku() ),
439 esc_attr( $product->get_name() ),
440 esc_attr( $product->get_price() ),
441 wp_kses_post( $args['text'] ),
442 $counter_element_classes,
443 $product_quantity
444 );
445 }
446
447 /**
448 * Get update cart item button.
449 *
450 * @param string $cart_item_key The cart item key.
451 * @param WC_Product $product The product to be edited.
452 * @param string $classes Button classes.
453 *
454 * @return string
455 */
456 public static function get_update_cart_item_button( $cart_item_key, $product, $classes = '' ) {
457 $cart_item = WC()->cart->get_cart_item( $cart_item_key );
458
459 $args = array(
460 'trigger' => 'update-cart-item',
461 'product_id' => $cart_item['product_id'],
462 'cart_item_key' => $cart_item_key,
463 'variation_id' => $cart_item['variation_id'],
464 'variation_attributes' => empty( array_filter( $cart_item['variation'] ) ) ? false : wp_json_encode( $cart_item['variation'] ),
465 'product_type' => $product->get_type(),
466 'text' => __( 'Update', 'orderable' ),
467 'classes' => $classes,
468 );
469
470 /**
471 * Filter arguments used to the update cart item button.
472 *
473 * @param array $args The arguments.
474 * @param string $cart_item_key The cart item key.
475 *
476 * @return array New arguments
477 * @since 1.4.0
478 * @hook orderable_update_cart_item_button_args
479 */
480 $args = apply_filters( 'orderable_update_cart_item_button_args', $args, $cart_item_key );
481
482 ob_start();
483 ?>
484 <button
485 class="orderable-button orderable-product__cancel-update"
486 data-orderable-trigger="show-cart"
487 >
488 <?php echo esc_html__( 'Cancel', 'orderable' ); ?>
489 </button>
490 <button
491 class="orderable-button <?php echo esc_attr( $args['classes'] ); ?>"
492 data-orderable-trigger="<?php echo esc_attr( $args['trigger'] ); ?>"
493 data-orderable-cart-item-key="<?php echo esc_attr( $args['cart_item_key'] ); ?>"
494 data-orderable-product-id="<?php echo esc_attr( $args['product_id'] ); ?>"
495 data-orderable-variation-id="<?php echo esc_attr( $args['variation_id'] ); ?>"
496 data-orderable-variation-attributes="<?php echo esc_attr( $args['variation_attributes'] ); ?>"
497 data-orderable-product-type="<?php echo esc_attr( $args['product_type'] ); ?>"
498 >
499 <?php echo esc_html( $args['text'] ); ?>
500 </button>
501 <?php
502
503 /**
504 * Filter the Update Cart Item button HTML.
505 *
506 * @param string|false $update_cart_item_button_html The Update Cart Item button HTML.
507 *
508 * @return string|false New HTML
509 * @since 1.4.0
510 * @hook orderable_update_cart_item_button_html
511 */
512 $html = apply_filters( 'orderable_update_cart_item_button_html', ob_get_clean() );
513
514 return $html;
515 }
516
517 /**
518 * Get add to cart trigger value.
519 *
520 * @param WC_Product $product
521 *
522 * @return string
523 */
524 public static function get_add_to_cart_trigger( $product ) {
525 $trigger = $product->is_type( 'variable' ) ? 'product-options' : 'add-to-cart';
526
527 return apply_filters( 'orderable_get_add_to_cart_trigger', $trigger, $product );
528 }
529
530 /**
531 * Get a list of attributes for available variations.
532 *
533 * @param WC_Product_Variable $product
534 *
535 * @return array
536 */
537 public static function get_available_variation_attributes( $product ) {
538 $available_variation_attributes = array();
539 $available_variations = $product->get_available_variations();
540 $available_variations = wc_list_pluck( $available_variations, 'attributes' );
541
542 if ( empty( $available_variations ) ) {
543 return $available_variation_attributes;
544 }
545
546 foreach ( $available_variations as $available_variation ) {
547 foreach ( $available_variation as $attribute_slug => $attribute_value ) {
548 if ( ! isset( $available_variation_attributes[ $attribute_slug ] ) ) {
549 $available_variation_attributes[ $attribute_slug ] = array();
550 }
551
552 $available_variation_attributes[ $attribute_slug ][] = $attribute_value;
553 }
554 }
555
556 return $available_variation_attributes;
557 }
558
559 /**
560 * Get available attributes.
561 *
562 * This method remove an attributes which don't belong to
563 * an active variation.
564 *
565 * @param WC_Product_Variable $product
566 *
567 * @return array
568 */
569 public static function get_available_attributes( $product ) {
570 $available_attributes = array();
571 $attributes = $product->get_variation_attributes();
572 $available_variation_attributes = self::get_available_variation_attributes( $product );
573
574 if ( empty( $attributes ) ) {
575 return $available_attributes;
576 }
577
578 foreach ( $attributes as $attribute_name => $attribute_terms ) {
579 $attribute_name_sanitized = wc_variation_attribute_name( $attribute_name );
580
581 if ( empty( $available_variation_attributes[ $attribute_name_sanitized ] ) ) {
582 continue;
583 }
584
585 if ( ! isset( $available_attributes[ $attribute_name ] ) ) {
586 $available_attributes[ $attribute_name ] = array();
587 }
588
589 foreach ( $attribute_terms as $attribute_term ) {
590 if ( ! in_array( $attribute_term, $available_variation_attributes[ $attribute_name_sanitized ], true ) && ! in_array( '', $available_variation_attributes[ $attribute_name_sanitized ], true ) ) {
591 continue;
592 }
593
594 $available_attributes[ $attribute_name ][] = $attribute_term;
595 }
596 }
597
598 return $available_attributes;
599 }
600
601 /**
602 * Is product single page hidden?
603 *
604 * @param int|WC_Product $product
605 *
606 * @return bool
607 */
608 public static function is_product_hidden( $product ) {
609 if ( is_numeric( $product ) ) {
610 $product = wc_get_product( $product );
611 }
612
613 if ( empty( $product ) ) {
614 return false;
615 }
616
617 $categories = $product->get_category_ids();
618
619 if ( empty( $categories ) ) {
620 return false;
621 }
622
623 foreach ( $categories as $category_id ) {
624 if ( self::is_category_hidden( $category_id ) ) {
625 return true;
626 }
627 }
628
629 return false;
630 }
631
632 /**
633 * Is this category hidden?
634 *
635 * @param $category_id
636 *
637 * @return bool
638 */
639 public static function is_category_hidden( $category_id ) {
640 $hidden_categories = Orderable_Settings::get_hidden_categories();
641
642 return in_array( $category_id, $hidden_categories, true );
643 }
644
645 /**
646 * Set if product is visible, based on category settings.
647 *
648 * @param bool $visible
649 * @param int $product_id
650 *
651 * @return bool
652 */
653 public static function set_product_visibility( $visible, $product_id ) {
654 $hidden = self::is_product_hidden( $product_id );
655
656 return $hidden ? false : $visible;
657 }
658
659 /**
660 * Disable cart permalink.
661 *
662 * @param string $permalink
663 *
664 * @return bool
665 */
666 public static function disable_cart_link( $permalink, $cart_item, $cart_item_key ) {
667 if ( ! isset( $cart_item['data'] ) ) {
668 return $permalink;
669 }
670
671 return self::is_product_hidden( $cart_item['data'] ) ? false : $permalink;
672 }
673
674 /**
675 * Set product page to 404 if required.
676 */
677 public static function products_404() {
678 if ( is_admin() || ! ( is_product() && is_single() ) ) {
679 return;
680 }
681
682 global $post;
683
684 if ( empty( $post ) ) {
685 return;
686 }
687
688 $product = wc_get_product( $post->ID );
689
690 if ( empty( $product ) || ! self::is_product_hidden( $product ) ) {
691 return;
692 }
693
694 global $wp_query;
695
696 $wp_query->set_404();
697 status_header( 404 );
698 nocache_headers();
699 }
700
701 /**
702 * Exclude hidden categories from queries.
703 *
704 * @param $tax_query
705 * @param $wc_query
706 *
707 * @return array
708 */
709 public static function remove_hidden_categories_from_products_query( $tax_query, $wc_query ) {
710 $hidden_categories = Orderable_Settings::get_hidden_categories();
711
712 if ( empty( $hidden_categories ) ) {
713 return $tax_query;
714 }
715
716 $tax_query[] = array(
717 'taxonomy' => 'product_cat',
718 'field' => 'term_id',
719 'terms' => $hidden_categories,
720 'operator' => 'NOT IN',
721 );
722
723 return $tax_query;
724 }
725
726 /**
727 * Remove hidden categories from terms query.
728 *
729 * As well as hiding from terms lists (sidebar widgets, etc),
730 * this also disables the archive page.
731 *
732 * @param array $args Args.
733 * @param array $taxonomies Taxonomies.
734 *
735 * @return mixed
736 */
737 public static function remove_hidden_categories_from_terms_query( $args, $taxonomies ) {
738 if ( is_admin() ) {
739 return $args;
740 }
741
742 $taxonomy = ! empty( $args['taxonomy'] ) && isset( $args['taxonomy'][0] ) ? $args['taxonomy'][0] : false;
743
744 if ( 'product_cat' !== $taxonomy || is_admin() ) {
745 return $args;
746 }
747
748 // Exclude hidden categories.
749 $hidden_categories = Orderable_Settings::get_hidden_categories();
750 $args['exclude'] = ! is_array( $args['exclude'] ) ? array() : $args['exclude'];
751 $args['exclude'] = array_merge( $args['exclude'], $hidden_categories );
752
753 return $args;
754 }
755
756 /**
757 * Remove hidden products from sitemap.
758 *
759 * @param array $query_args Query args.
760 * @param string $post_type Post type.
761 *
762 * @return mixed
763 */
764 public static function remove_hidden_products_from_sitemap( $query_args, $post_type ) {
765 if ( 'product' !== $post_type ) {
766 return $query_args;
767 }
768
769 $hidden_categories = Orderable_Settings::get_hidden_categories();
770
771 if ( empty( $hidden_categories ) ) {
772 return $query_args;
773 }
774
775 $tax_query = array(
776 'taxonomy' => 'product_cat',
777 'terms' => $hidden_categories,
778 'field' => 'term_id',
779 'include_children' => true,
780 'operator' => 'NOT IN',
781 );
782
783 if ( ! isset( $query_args['tax_query'] ) ) {
784 $query_args['tax_query'] = array();
785 }
786
787 $query_args['tax_query'][] = $tax_query;
788
789 return $query_args;
790 }
791
792 /**
793 * Exclude hidden categories from sitemap.
794 *
795 * @param array $query_args Query args.
796 * @param string $taxonomy Taxonomy.
797 *
798 * @return mixed
799 */
800 public static function remove_hidden_categories_from_sitemap( $query_args, $taxonomy ) {
801 if ( 'product_cat' !== $taxonomy ) {
802 return $query_args;
803 }
804
805 $hidden_categories = Orderable_Settings::get_hidden_categories();
806
807 if ( empty( $hidden_categories ) ) {
808 return $query_args;
809 }
810
811 if ( ! isset( $query_args['exclude'] ) ) {
812 $query_args['exclude'] = $hidden_categories;
813 } else {
814 if ( is_array( $hidden_categories ) ) {
815 $query_args['exclude'] = array_merge( $query_args['exclude'], $hidden_categories );
816 } else {
817 $query_args['exclude'] = $query_args['exclude'] . ',' . implode( ',', $hidden_categories );
818 }
819 }
820
821 return $query_args;
822 }
823
824 /**
825 * Get product accordion data.
826 *
827 * @param WC_Product $product Product.
828 *
829 * @return array
830 */
831 public static function get_accordion_data( $product ) {
832 $data = array();
833
834 $description = Orderable_Settings::get_setting( 'drawer_quickview_description' );
835
836 if ( 'none' !== $description ) {
837 $description = 'short' === $description ? $product->get_short_description() : $product->get_description();
838
839 $data[] = array(
840 'title' => __( 'Description', 'orderable' ),
841 'content' => apply_filters( 'the_content', $description ),
842 'id' => 'accordion-description',
843 );
844 }
845
846 /**
847 * Filter product accordion data.
848 *
849 * @var array $data
850 * @var WC_Product $product
851 */
852 return apply_filters( 'orderable_get_accordion_data', $data, $product );
853 }
854
855 /**
856 * Update the button args to allow adding to the cart without opening side drawer.
857 *
858 * @param array $args The button args.
859 * @param WC_Product $product The product.
860 * @param array $layout_settings The product layout settings.
861 * @return array
862 */
863 public static function update_button_args_to_allow_add_to_cart_without_side_drawer( $args, $product, $layout_settings ) {
864 if ( empty( $layout_settings['quantity_roller'] ) ) {
865 return $args;
866 }
867
868 if ( 'add-to-cart' !== $args['trigger'] ) {
869 return $args;
870 }
871
872 $args['trigger'] = 'add-to-cart-without-side-drawer';
873
874 return $args;
875 }
876
877 /**
878 * Update the quantity roller fragments.
879 *
880 * @param array $fragments The WooCommerce cart fragments.
881 * @return array
882 */
883 public static function handle_adding_product_without_side_drawer( $fragments ) {
884 // phpcs:ignore WordPress.Security.NonceVerification
885 if ( empty( $_POST['action'] ) || empty( $_POST['product_id'] ) ) {
886 return $fragments;
887 }
888
889 $action = sanitize_text_field( wp_unslash( $_POST['action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
890 $product_id = absint( sanitize_text_field( wp_unslash( $_POST['product_id'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
891
892 switch ( $action ) {
893 case 'orderable_add_to_cart':
894 $cart_item = false;
895 foreach ( WC()->cart->get_cart() as $cart_item_data ) {
896 if ( $product_id !== $cart_item_data['product_id'] ) {
897 continue;
898 }
899
900 $cart_item = $cart_item_data;
901 }
902
903 if ( empty( $cart_item ) ) {
904 return $fragments;
905 }
906
907 ob_start();
908 self::get_quantity_roller( $cart_item );
909 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-quantity-roller" ] = ob_get_clean();
910
911 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
912
913 break;
914
915 case 'orderable_cart_quantity':
916 // phpcs:ignore WordPress.Security.NonceVerification
917 if ( empty( $_POST['cart_item_key'] ) || ! isset( $_POST['quantity'] ) ) {
918 return $fragments;
919 }
920
921 $cart_item_key = sanitize_text_field( wp_unslash( $_POST['cart_item_key'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
922
923 if ( empty( $cart_item_key ) ) {
924 return $fragments;
925 }
926
927 $cart_item = WC()->cart->get_cart_item( $cart_item_key );
928
929 ob_start();
930 self::get_quantity_roller( $cart_item );
931 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-quantity-roller" ] = ob_get_clean();
932
933 if ( empty( $cart_item ) ) {
934 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
935
936 return $fragments;
937 }
938
939 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
940
941 break;
942
943 default:
944 break;
945 }
946
947 return $fragments;
948 }
949
950 /**
951 * Get the HTML markup for the quantity roller element.
952 *
953 * @param array $cart_item The cart item.
954 * @param string $product_price The product price.
955 * @return void
956 */
957 public static function get_quantity_roller( $cart_item, $product_price = '' ) {
958 if ( empty( $cart_item ) ) {
959 ?>
960 <div class="orderable-quantity-roller"></div>
961 <?php
962 } else {
963 ?>
964 <div class="orderable-quantity-roller orderable-quantity-roller--is-active">
965 <span class="orderable-quantity-roller__roller">
966 <button
967 class="orderable-quantity-roller__button orderable-quantity-roller__button--decrease"
968 data-orderable-trigger="decrease-quantity"
969 data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
970 data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
971 data-orderable-quantity="<?php echo esc_attr( $cart_item['quantity'] ); ?>"
972 ><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><rect x="0" fill="none" width="20" height="20"/><g><path d="M12 4h3c.6 0 1 .4 1 1v1H3V5c0-.6.5-1 1-1h3c.2-1.1 1.3-2 2.5-2s2.3.9 2.5 2zM8 4h3c-.2-.6-.9-1-1.5-1S8.2 3.4 8 4zM4 7h11l-.9 10.1c0 .5-.5.9-1 .9H5.9c-.5 0-.9-.4-1-.9L4 7z"/></g></svg></button>
973 <span
974 class="orderable-quantity-roller__quantity"
975 contenteditable="true"
976 inputmode="numeric"
977 data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
978 data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
979 >
980 <?php echo esc_attr( $cart_item['quantity'] ); ?>
981 </span>
982 <button
983 class="orderable-quantity-roller__button orderable-quantity-roller__button--increase"
984 data-orderable-trigger="increase-quantity"
985 data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
986 data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
987 data-orderable-quantity="<?php echo esc_attr( $cart_item['quantity'] ); ?>"
988 >+</button>
989 </span>
990 <?php if ( ! empty( $product_price ) ) : ?>
991 <span class="orderable-quantity-roller__price"><?php echo wp_kses_post( $product_price ); ?></span>
992 <?php endif; ?>
993 </div>
994 <?php
995 }
996 }
997
998 /**
999 * Get the HTML markup for the product counter element.
1000 *
1001 * @param int $product_id The product ID.
1002 * @return string The HTML markup.
1003 */
1004 public static function get_product_counter( $product_id ) {
1005 $quantity = Orderable_Helpers::get_product_quantity_in_the_cart( absint( $product_id ) );
1006
1007 ?>
1008 <span
1009 class="orderable-product__actions-counter"
1010 data-orderable-product-quantity="<?php echo esc_attr( $quantity ); ?>"
1011 style="animation: wobble-hor-bottom .8s both;"
1012 >
1013 <?php echo esc_html( $quantity ); ?>
1014 </span>
1015
1016 <?php
1017
1018 $html_markup = ob_get_clean();
1019
1020 /**
1021 * Filter the product counter HTML markup.
1022 *
1023 * @param string $html_markup The HTML markup.
1024 *
1025 * @return string New value.
1026 * @since 1.7.0
1027 * @hook orderable_product_counter_html
1028 */
1029 return apply_filters( 'orderable_product_counter_html', $html_markup );
1030 }
1031
1032 /**
1033 * Update product counter fragments for removed item.
1034 *
1035 * @param string $cart_item_key The removed cart item key.
1036 * @param WC_Cart $wc_cart The WC Cart.
1037 * @return void
1038 */
1039 public static function update_product_counter_fragments_for_removed_item( $cart_item_key, $wc_cart ) {
1040 $product_id = empty( $wc_cart->removed_cart_contents[ $cart_item_key ]['product_id'] ) ? false : $wc_cart->removed_cart_contents[ $cart_item_key ]['product_id'];
1041
1042 if ( ! $product_id ) {
1043 return;
1044 }
1045
1046 add_filter(
1047 'woocommerce_add_to_cart_fragments',
1048 function( $fragments ) use ( $product_id ) {
1049 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
1050
1051 return $fragments;
1052 }
1053 );
1054 }
1055
1056 /**
1057 * Add `.orderable-main--quantity-roller` class to the `.orderable-main` element.
1058 *
1059 * @param string $class The class attribute value.
1060 * @param array $args The layout settings.
1061 * @return string
1062 */
1063 public static function add_quantity_roller_class( $class, $args ) {
1064 if ( empty( $args['quantity_roller'] ) ) {
1065 return $class;
1066 }
1067
1068 $class .= ' orderable-main--quantity-roller';
1069
1070 return $class;
1071 }
1072 }
1073