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

1,085 lines 32.1 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 = [];
833
834 $description = Orderable_Settings::get_setting( 'drawer_quickview_description' );
835
836 if ( 'none' === $description ) {
837 // phpcs:ignore WooCommerce.Commenting.CommentHooks
838 return apply_filters( 'orderable_get_accordion_data', $data, $product );
839 }
840
841 $description = 'short' === $description ? $product->get_short_description() : $product->get_description();
842
843 // phpcs:ignore WooCommerce.Commenting.CommentHooks
844 $content = apply_filters( 'the_content', $description );
845
846 if ( empty( $content ) ) {
847 // phpcs:ignore WooCommerce.Commenting.CommentHooks
848 return apply_filters( 'orderable_get_accordion_data', $data, $product );
849 }
850
851 $data[] = array(
852 'title' => __( 'Description', 'orderable' ),
853 'content' => $content,
854 'id' => 'accordion-description',
855 );
856
857 /**
858 * Filter product accordion data.
859 *
860 * @var array $data
861 * @var WC_Product $product
862 * @since 1.0.0
863 */
864 return apply_filters( 'orderable_get_accordion_data', $data, $product );
865 }
866
867 /**
868 * Update the button args to allow adding to the cart without opening side drawer.
869 *
870 * @param array $args The button args.
871 * @param WC_Product $product The product.
872 * @param array $layout_settings The product layout settings.
873 * @return array
874 */
875 public static function update_button_args_to_allow_add_to_cart_without_side_drawer( $args, $product, $layout_settings ) {
876 if ( empty( $layout_settings['quantity_roller'] ) ) {
877 return $args;
878 }
879
880 if ( 'add-to-cart' !== $args['trigger'] ) {
881 return $args;
882 }
883
884 $args['trigger'] = 'add-to-cart-without-side-drawer';
885
886 return $args;
887 }
888
889 /**
890 * Update the quantity roller fragments.
891 *
892 * @param array $fragments The WooCommerce cart fragments.
893 * @return array
894 */
895 public static function handle_adding_product_without_side_drawer( $fragments ) {
896 // phpcs:ignore WordPress.Security.NonceVerification
897 if ( empty( $_POST['action'] ) || empty( $_POST['product_id'] ) ) {
898 return $fragments;
899 }
900
901 $action = sanitize_text_field( wp_unslash( $_POST['action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
902 $product_id = absint( sanitize_text_field( wp_unslash( $_POST['product_id'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification
903
904 switch ( $action ) {
905 case 'orderable_add_to_cart':
906 $cart_item = false;
907 foreach ( WC()->cart->get_cart() as $cart_item_data ) {
908 if ( $product_id !== $cart_item_data['product_id'] ) {
909 continue;
910 }
911
912 $cart_item = $cart_item_data;
913 }
914
915 if ( empty( $cart_item ) ) {
916 return $fragments;
917 }
918
919 ob_start();
920 self::get_quantity_roller( $cart_item );
921 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-quantity-roller" ] = ob_get_clean();
922
923 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
924
925 break;
926
927 case 'orderable_cart_quantity':
928 // phpcs:ignore WordPress.Security.NonceVerification
929 if ( empty( $_POST['cart_item_key'] ) || ! isset( $_POST['quantity'] ) ) {
930 return $fragments;
931 }
932
933 $cart_item_key = sanitize_text_field( wp_unslash( $_POST['cart_item_key'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
934
935 if ( empty( $cart_item_key ) ) {
936 return $fragments;
937 }
938
939 $cart_item = WC()->cart->get_cart_item( $cart_item_key );
940
941 ob_start();
942 self::get_quantity_roller( $cart_item );
943 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-quantity-roller" ] = ob_get_clean();
944
945 if ( empty( $cart_item ) ) {
946 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
947
948 return $fragments;
949 }
950
951 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
952
953 break;
954
955 default:
956 break;
957 }
958
959 return $fragments;
960 }
961
962 /**
963 * Get the HTML markup for the quantity roller element.
964 *
965 * @param array $cart_item The cart item.
966 * @param string $product_price The product price.
967 * @return void
968 */
969 public static function get_quantity_roller( $cart_item, $product_price = '' ) {
970 if ( empty( $cart_item ) ) {
971 ?>
972 <div class="orderable-quantity-roller"></div>
973 <?php
974 } else {
975 ?>
976 <div class="orderable-quantity-roller orderable-quantity-roller--is-active">
977 <span class="orderable-quantity-roller__roller">
978 <button
979 class="orderable-quantity-roller__button orderable-quantity-roller__button--decrease"
980 data-orderable-trigger="decrease-quantity"
981 data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
982 data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
983 data-orderable-quantity="<?php echo esc_attr( $cart_item['quantity'] ); ?>"
984 ><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>
985 <span
986 class="orderable-quantity-roller__quantity"
987 contenteditable="true"
988 inputmode="numeric"
989 data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
990 data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
991 >
992 <?php echo esc_attr( $cart_item['quantity'] ); ?>
993 </span>
994 <button
995 class="orderable-quantity-roller__button orderable-quantity-roller__button--increase"
996 data-orderable-trigger="increase-quantity"
997 data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
998 data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
999 data-orderable-quantity="<?php echo esc_attr( $cart_item['quantity'] ); ?>"
1000 >+</button>
1001 </span>
1002 <?php if ( ! empty( $product_price ) ) : ?>
1003 <span class="orderable-quantity-roller__price"><?php echo wp_kses_post( $product_price ); ?></span>
1004 <?php endif; ?>
1005 </div>
1006 <?php
1007 }
1008 }
1009
1010 /**
1011 * Get the HTML markup for the product counter element.
1012 *
1013 * @param int $product_id The product ID.
1014 * @return string The HTML markup.
1015 */
1016 public static function get_product_counter( $product_id ) {
1017 $quantity = Orderable_Helpers::get_product_quantity_in_the_cart( absint( $product_id ) );
1018
1019 ?>
1020 <span
1021 class="orderable-product__actions-counter"
1022 data-orderable-product-quantity="<?php echo esc_attr( $quantity ); ?>"
1023 style="animation: wobble-hor-bottom .8s both;"
1024 >
1025 <?php echo esc_html( $quantity ); ?>
1026 </span>
1027
1028 <?php
1029
1030 $html_markup = ob_get_clean();
1031
1032 /**
1033 * Filter the product counter HTML markup.
1034 *
1035 * @param string $html_markup The HTML markup.
1036 *
1037 * @return string New value.
1038 * @since 1.7.0
1039 * @hook orderable_product_counter_html
1040 */
1041 return apply_filters( 'orderable_product_counter_html', $html_markup );
1042 }
1043
1044 /**
1045 * Update product counter fragments for removed item.
1046 *
1047 * @param string $cart_item_key The removed cart item key.
1048 * @param WC_Cart $wc_cart The WC Cart.
1049 * @return void
1050 */
1051 public static function update_product_counter_fragments_for_removed_item( $cart_item_key, $wc_cart ) {
1052 $product_id = empty( $wc_cart->removed_cart_contents[ $cart_item_key ]['product_id'] ) ? false : $wc_cart->removed_cart_contents[ $cart_item_key ]['product_id'];
1053
1054 if ( ! $product_id ) {
1055 return;
1056 }
1057
1058 add_filter(
1059 'woocommerce_add_to_cart_fragments',
1060 function( $fragments ) use ( $product_id ) {
1061 $fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );
1062
1063 return $fragments;
1064 }
1065 );
1066 }
1067
1068 /**
1069 * Add `.orderable-main--quantity-roller` class to the `.orderable-main` element.
1070 *
1071 * @param string $class The class attribute value.
1072 * @param array $args The layout settings.
1073 * @return string
1074 */
1075 public static function add_quantity_roller_class( $class, $args ) {
1076 if ( empty( $args['quantity_roller'] ) ) {
1077 return $class;
1078 }
1079
1080 $class .= ' orderable-main--quantity-roller';
1081
1082 return $class;
1083 }
1084 }
1085