PluginProbe
Orderable – Restaurant & Food Ordering System / 1.12.2
Orderable – Restaurant & Food Ordering System v1.12.2
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.12.2, at inc/class-products.php

1,072 lines 31.6 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 array $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 *
475 * @return array New arguments
476 * @since 1.4.0
477 * @hook orderable_update_cart_item_button_args
478 */
479 $args = apply_filters( 'orderable_update_cart_item_button_args', $args, $cart_item_key );
480
481 ob_start();
482 ?>
483 <button
484 class="orderable-button orderable-product__cancel-update"
485 data-orderable-trigger="show-cart"
486 >
487 <?php echo esc_html__( 'Cancel', 'orderable' ); ?>
488 </button>
489 <button
490 class="orderable-button <?php echo esc_attr( $args['classes'] ); ?>"
491 data-orderable-trigger="<?php echo esc_attr( $args['trigger'] ); ?>"
492 data-orderable-cart-item-key="<?php echo esc_attr( $args['cart_item_key'] ); ?>"
493 data-orderable-product-id="<?php echo esc_attr( $args['product_id'] ); ?>"
494 data-orderable-variation-id="<?php echo esc_attr( $args['variation_id'] ); ?>"
495 data-orderable-variation-attributes="<?php echo esc_attr( $args['variation_attributes'] ); ?>"
496 data-orderable-product-type="<?php echo esc_attr( $args['product_type'] ); ?>"
497 >
498 <?php echo esc_html( $args['text'] ); ?>
499 </button>
500 <?php
501
502 /**
503 * Filter the Update Cart Item button HTML.
504 *
505 * @param array $update_cart_item_button_html The Update Cart Item button HTML.
506 *
507 * @return array New HTML
508 * @since 1.4.0
509 * @hook orderable_update_cart_item_button_html
510 */
511 $html = apply_filters( 'orderable_update_cart_item_button_html', ob_get_clean() );
512
513 return $html;
514 }
515
516 /**
517 * Get add to cart trigger value.
518 *
519 * @param WC_Product $product
520 *
521 * @return string
522 */
523 public static function get_add_to_cart_trigger( $product ) {
524 $trigger = $product->is_type( 'variable' ) ? 'product-options' : 'add-to-cart';
525
526 return apply_filters( 'orderable_get_add_to_cart_trigger', $trigger, $product );
527 }
528
529 /**
530 * Get a list of attributes for available variations.
531 *
532 * @param WC_Product_Variable $product
533 *
534 * @return array
535 */
536 public static function get_available_variation_attributes( $product ) {
537 $available_variation_attributes = array();
538 $available_variations = $product->get_available_variations();
539 $available_variations = wc_list_pluck( $available_variations, 'attributes' );
540
541 if ( empty( $available_variations ) ) {
542 return $available_variation_attributes;
543 }
544
545 foreach ( $available_variations as $available_variation ) {
546 foreach ( $available_variation as $attribute_slug => $attribute_value ) {
547 if ( ! isset( $available_variation_attributes[ $attribute_slug ] ) ) {
548 $available_variation_attributes[ $attribute_slug ] = array();
549 }
550
551 $available_variation_attributes[ $attribute_slug ][] = $attribute_value;
552 }
553 }
554
555 return $available_variation_attributes;
556 }
557
558 /**
559 * Get available attributes.
560 *
561 * This method remove an attributes which don't belong to
562 * an active variation.
563 *
564 * @param WC_Product_Variable $product
565 *
566 * @return array
567 */
568 public static function get_available_attributes( $product ) {
569 $available_attributes = array();
570 $attributes = $product->get_variation_attributes();
571 $available_variation_attributes = self::get_available_variation_attributes( $product );
572
573 if ( empty( $attributes ) ) {
574 return $available_attributes;
575 }
576
577 foreach ( $attributes as $attribute_name => $attribute_terms ) {
578 $attribute_name_sanitized = wc_variation_attribute_name( $attribute_name );
579
580 if ( empty( $available_variation_attributes[ $attribute_name_sanitized ] ) ) {
581 continue;
582 }
583
584 if ( ! isset( $available_attributes[ $attribute_name ] ) ) {
585 $available_attributes[ $attribute_name ] = array();
586 }
587
588 foreach ( $attribute_terms as $attribute_term ) {
589 if ( ! in_array( $attribute_term, $available_variation_attributes[ $attribute_name_sanitized ], true ) && ! in_array( '', $available_variation_attributes[ $attribute_name_sanitized ], true ) ) {
590 continue;
591 }
592
593 $available_attributes[ $attribute_name ][] = $attribute_term;
594 }
595 }
596
597 return $available_attributes;
598 }
599
600 /**
601 * Is product single page hidden?
602 *
603 * @param int|WC_Product $product
604 *
605 * @return bool
606 */
607 public static function is_product_hidden( $product ) {
608 if ( is_numeric( $product ) ) {
609 $product = wc_get_product( $product );
610 }
611
612 if ( empty( $product ) ) {
613 return false;
614 }
615
616 $categories = $product->get_category_ids();
617
618 if ( empty( $categories ) ) {
619 return false;
620 }
621
622 foreach ( $categories as $category_id ) {
623 if ( self::is_category_hidden( $category_id ) ) {
624 return true;
625 }
626 }
627
628 return false;
629 }
630
631 /**
632 * Is this category hidden?
633 *
634 * @param $category_id
635 *
636 * @return bool
637 */
638 public static function is_category_hidden( $category_id ) {
639 $hidden_categories = Orderable_Settings::get_hidden_categories();
640
641 return in_array( $category_id, $hidden_categories, true );
642 }
643
644 /**
645 * Set if product is visible, based on category settings.
646 *
647 * @param bool $visible
648 * @param int $product_id
649 *
650 * @return bool
651 */
652 public static function set_product_visibility( $visible, $product_id ) {
653 $hidden = self::is_product_hidden( $product_id );
654
655 return $hidden ? false : $visible;
656 }
657
658 /**
659 * Disable cart permalink.
660 *
661 * @param string $permalink
662 *
663 * @return bool
664 */
665 public static function disable_cart_link( $permalink, $cart_item, $cart_item_key ) {
666 if ( ! isset( $cart_item['data'] ) ) {
667 return $permalink;
668 }
669
670 return self::is_product_hidden( $cart_item['data'] ) ? false : $permalink;
671 }
672
673 /**
674 * Set product page to 404 if required.
675 */
676 public static function products_404() {
677 if ( is_admin() || ! ( is_product() && is_single() ) ) {
678 return;
679 }
680
681 global $post;
682
683 if ( empty( $post ) ) {
684 return;
685 }
686
687 $product = wc_get_product( $post->ID );
688
689 if ( empty( $product ) || ! self::is_product_hidden( $product ) ) {
690 return;
691 }
692
693 global $wp_query;
694
695 $wp_query->set_404();
696 status_header( 404 );
697 nocache_headers();
698 }
699
700 /**
701 * Exclude hidden categories from queries.
702 *
703 * @param $tax_query
704 * @param $wc_query
705 *
706 * @return array
707 */
708 public static function remove_hidden_categories_from_products_query( $tax_query, $wc_query ) {
709 $hidden_categories = Orderable_Settings::get_hidden_categories();
710
711 if ( empty( $hidden_categories ) ) {
712 return $tax_query;
713 }
714
715 $tax_query[] = array(
716 'taxonomy' => 'product_cat',
717 'field' => 'term_id',
718 'terms' => $hidden_categories,
719 'operator' => 'NOT IN',
720 );
721
722 return $tax_query;
723 }
724
725 /**
726 * Remove hidden categories from terms query.
727 *
728 * As well as hiding from terms lists (sidebar widgets, etc),
729 * this also disables the archive page.
730 *
731 * @param array $args Args.
732 * @param array $taxonomies Taxonomies.
733 *
734 * @return mixed
735 */
736 public static function remove_hidden_categories_from_terms_query( $args, $taxonomies ) {
737 if ( is_admin() ) {
738 return $args;
739 }
740
741 $taxonomy = ! empty( $args['taxonomy'] ) && isset( $args['taxonomy'][0] ) ? $args['taxonomy'][0] : false;
742
743 if ( 'product_cat' !== $taxonomy || is_admin() ) {
744 return $args;
745 }
746
747 // Exclude hidden categories.
748 $hidden_categories = Orderable_Settings::get_hidden_categories();
749 $args['exclude'] = ! is_array( $args['exclude'] ) ? array() : $args['exclude'];
750 $args['exclude'] = array_merge( $args['exclude'], $hidden_categories );
751
752 return $args;
753 }
754
755 /**
756 * Remove hidden products from sitemap.
757 *
758 * @param array $query_args Query args.
759 * @param string $post_type Post type.
760 *
761 * @return mixed
762 */
763 public static function remove_hidden_products_from_sitemap( $query_args, $post_type ) {
764 if ( 'product' !== $post_type ) {
765 return $query_args;
766 }
767
768 $hidden_categories = Orderable_Settings::get_hidden_categories();
769
770 if ( empty( $hidden_categories ) ) {
771 return $query_args;
772 }
773
774 $tax_query = array(
775 'taxonomy' => 'product_cat',
776 'terms' => $hidden_categories,
777 'field' => 'term_id',
778 'include_children' => true,
779 'operator' => 'NOT IN',
780 );
781
782 if ( ! isset( $query_args['tax_query'] ) ) {
783 $query_args['tax_query'] = array();
784 }
785
786 $query_args['tax_query'][] = $tax_query;
787
788 return $query_args;
789 }
790
791 /**
792 * Exclude hidden categories from sitemap.
793 *
794 * @param array $query_args Query args.
795 * @param string $taxonomy Taxonomy.
796 *
797 * @return mixed
798 */
799 public static function remove_hidden_categories_from_sitemap( $query_args, $taxonomy ) {
800 if ( 'product_cat' !== $taxonomy ) {
801 return $query_args;
802 }
803
804 $hidden_categories = Orderable_Settings::get_hidden_categories();
805
806 if ( empty( $hidden_categories ) ) {
807 return $query_args;
808 }
809
810 if ( ! isset( $query_args['exclude'] ) ) {
811 $query_args['exclude'] = $hidden_categories;
812 } else {
813 if ( is_array( $hidden_categories ) ) {
814 $query_args['exclude'] = array_merge( $query_args['exclude'], $hidden_categories );
815 } else {
816 $query_args['exclude'] = $query_args['exclude'] . ',' . implode( ',', $hidden_categories );
817 }
818 }
819
820 return $query_args;
821 }
822
823 /**
824 * Get product accordion data.
825 *
826 * @param WC_Product $product Product.
827 *
828 * @return array
829 */
830 public static function get_accordion_data( $product ) {
831 $data = array();
832
833 $description = Orderable_Settings::get_setting( 'drawer_quickview_description' );
834
835 if ( 'none' !== $description ) {
836 $description = 'short' === $description ? $product->get_short_description() : $product->get_description();
837
838 $data[] = array(
839 'title' => __( 'Description', 'orderable' ),
840 'content' => apply_filters( 'the_content', $description ),
841 'id' => 'accordion-description',
842 );
843 }
844
845 /**
846 * Filter product accordion data.
847 *
848 * @var array $data
849 * @var WC_Product $product
850 */
851 return apply_filters( 'orderable_get_accordion_data', $data, $product );
852 }
853
854 /**
855 * Update the button args to allow adding to the cart without opening side drawer.
856 *
857 * @param array $args The button args.
858 * @param WC_Product $product The product.
859 * @param array $layout_settings The product layout settings.
860 * @return array
861 */
862 public static function update_button_args_to_allow_add_to_cart_without_side_drawer( $args, $product, $layout_settings ) {
863 if ( empty( $layout_settings['quantity_roller'] ) ) {
864 return $args;
865 }
866
867 if ( 'add-to-cart' !== $args['trigger'] ) {
868 return $args;
869 }
870
871 $args['trigger'] = 'add-to-cart-without-side-drawer';
872
873 return $args;
874 }
875
876 /**
877 * Update the quantity roller fragments.
878 *
879 * @param array $fragments The WooCommerce cart fragments.
880 * @return array
881 */
882 public static function handle_adding_product_without_side_drawer( $fragments ) {
883 // phpcs:ignore WordPress.Security.NonceVerification
884 if ( empty( $_POST['action'] ) || empty( $_POST['product_id'] ) ) {
885 return $fragments;
886 }
887
888 $action = sanitize_text_field( wp_unslash( $_POST['action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
889 $product_id = absint( sanitize_text_field( wp_unslash( $_POST['product_id'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
890
891 switch ( $action ) {
892 case 'orderable_add_to_cart':
893 $cart_item = false;
894 foreach ( WC()->cart->get_cart() as $cart_item_data ) {
895 if ( $product_id !== $cart_item_data['product_id'] ) {
896 continue;
897 }
898
899 $cart_item = $cart_item_data;
900 }
901
902 if ( empty( $cart_item ) ) {
903 return $fragments;
904 }
905
906 ob_start();
907 self::get_quantity_roller( $cart_item );
908 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-quantity-roller" ] = ob_get_clean();
909
910 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
911
912 break;
913
914 case 'orderable_cart_quantity':
915 // phpcs:ignore WordPress.Security.NonceVerification
916 if ( empty( $_POST['cart_item_key'] ) || ! isset( $_POST['quantity'] ) ) {
917 return $fragments;
918 }
919
920 $cart_item_key = sanitize_text_field( wp_unslash( $_POST['cart_item_key'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
921
922 if ( empty( $cart_item_key ) ) {
923 return $fragments;
924 }
925
926 $cart_item = WC()->cart->get_cart_item( $cart_item_key );
927
928 ob_start();
929 self::get_quantity_roller( $cart_item );
930 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-quantity-roller" ] = ob_get_clean();
931
932 if ( empty( $cart_item ) ) {
933 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
934
935 return $fragments;
936 }
937
938 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
939
940 break;
941
942 default:
943 break;
944 }
945
946 return $fragments;
947 }
948
949 /**
950 * Get the HTML markup for the quantity roller element.
951 *
952 * @param array $cart_item The cart item.
953 * @param string $product_price The product price.
954 * @return void
955 */
956 public static function get_quantity_roller( $cart_item, $product_price = '' ) {
957 if ( empty( $cart_item ) ) {
958 ?>
959 <div class="orderable-quantity-roller"></div>
960 <?php
961 } else {
962 ?>
963 <div class="orderable-quantity-roller orderable-quantity-roller--is-active">
964 <span class="orderable-quantity-roller__roller">
965 <button
966 class="orderable-quantity-roller__button orderable-quantity-roller__button--decrease"
967 data-orderable-trigger="decrease-quantity"
968 data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
969 data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
970 data-orderable-quantity="<?php echo esc_attr( $cart_item['quantity'] ); ?>"
971 ><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>
972 <span
973 class="orderable-quantity-roller__quantity"
974 contenteditable="true"
975 inputmode="numeric"
976 data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
977 data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
978 >
979 <?php echo esc_attr( $cart_item['quantity'] ); ?>
980 </span>
981 <button
982 class="orderable-quantity-roller__button orderable-quantity-roller__button--increase"
983 data-orderable-trigger="increase-quantity"
984 data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
985 data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
986 data-orderable-quantity="<?php echo esc_attr( $cart_item['quantity'] ); ?>"
987 >+</button>
988 </span>
989 <?php if ( ! empty( $product_price ) ) : ?>
990 <span class="orderable-quantity-roller__price"><?php echo wp_kses_post( $product_price ); ?></span>
991 <?php endif; ?>
992 </div>
993 <?php
994 }
995 }
996
997 /**
998 * Get the HTML markup for the product counter element.
999 *
1000 * @param int $product_id The product ID.
1001 * @return string The HTML markup.
1002 */
1003 public static function get_product_counter( $product_id ) {
1004 $quantity = Orderable_Helpers::get_product_quantity_in_the_cart( absint( $product_id ) );
1005
1006 ?>
1007 <span
1008 class="orderable-product__actions-counter"
1009 data-orderable-product-quantity="<?php echo esc_attr( $quantity ); ?>"
1010 style="animation: wobble-hor-bottom .8s both;"
1011 >
1012 <?php echo esc_html( $quantity ); ?>
1013 </span>
1014
1015 <?php
1016
1017 $html_markup = ob_get_clean();
1018
1019 /**
1020 * Filter the product counter HTML markup.
1021 *
1022 * @param string $html_markup The HTML markup.
1023 *
1024 * @return string New value.
1025 * @since 1.7.0
1026 * @hook orderable_product_counter_html
1027 */
1028 return apply_filters( 'orderable_product_counter_html', $html_markup );
1029 }
1030
1031 /**
1032 * Update product counter fragments for removed item.
1033 *
1034 * @param string $cart_item_key The removed cart item key.
1035 * @param WC_Cart $wc_cart The WC Cart.
1036 * @return void
1037 */
1038 public static function update_product_counter_fragments_for_removed_item( $cart_item_key, $wc_cart ) {
1039 $product_id = empty( $wc_cart->removed_cart_contents[ $cart_item_key ]['product_id'] ) ? false : $wc_cart->removed_cart_contents[ $cart_item_key ]['product_id'];
1040
1041 if ( ! $product_id ) {
1042 return;
1043 }
1044
1045 add_filter(
1046 'woocommerce_add_to_cart_fragments',
1047 function( $fragments ) use ( $product_id ) {
1048 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
1049
1050 return $fragments;
1051 }
1052 );
1053 }
1054
1055 /**
1056 * Add `.orderable-main--quantity-roller` class to the `.orderable-main` element.
1057 *
1058 * @param string $class The class attribute value.
1059 * @param array $args The layout settings.
1060 * @return string
1061 */
1062 public static function add_quantity_roller_class( $class, $args ) {
1063 if ( empty( $args['quantity_roller'] ) ) {
1064 return $class;
1065 }
1066
1067 $class .= ' orderable-main--quantity-roller';
1068
1069 return $class;
1070 }
1071 }
1072