File "class-products.php"

Full Path: /home/romayxjt/public_html/wp-content/plugins/orderable/inc/class-products.php
File size: 32.08 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * Product methods.
 *
 * @package Orderable/Classes
 */

defined( 'ABSPATH' ) || exit;

/**
 * Products class.
 */
class Orderable_Products {
	/**
	 * Init
	 */
	public static function run() {
		add_filter( 'woocommerce_format_price_range', array( __CLASS__, 'format_price_range' ), 10, 3 );
		add_filter( 'woocommerce_product_is_visible', array( __CLASS__, 'set_product_visibility' ), 10, 2 );
		add_action( 'template_redirect', array( __CLASS__, 'products_404' ) );
		add_filter( 'woocommerce_cart_item_permalink', array( __CLASS__, 'disable_cart_link' ), 10, 3 );
		add_filter( 'woocommerce_product_query_tax_query', array( __CLASS__, 'remove_hidden_categories_from_products_query' ), 10, 2 );
		add_filter( 'get_terms_args', array( __CLASS__, 'remove_hidden_categories_from_terms_query' ), 10, 2 );
		add_filter( 'wp_sitemaps_posts_query_args', array( __CLASS__, 'remove_hidden_products_from_sitemap' ), 10, 2 );
		add_filter( 'wp_sitemaps_taxonomies_query_args', array( __CLASS__, 'remove_hidden_categories_from_sitemap' ), 10, 2 );
		add_filter( 'orderable_add_to_cart_button_args', array( __CLASS__, 'update_button_args_to_allow_add_to_cart_without_side_drawer' ), 10, 3 );
		add_filter( 'woocommerce_add_to_cart_fragments', array( __CLASS__, 'handle_adding_product_without_side_drawer' ) );
		add_filter( 'orderable_main_class', array( __CLASS__, 'add_quantity_roller_class' ), 10, 2 );

		add_action( 'woocommerce_cart_item_removed', array( __CLASS__, 'update_product_counter_fragments_for_removed_item' ), 10, 2 );
	}

	/**
	 * Format price range.
	 *
	 * @param string $price
	 * @param float  $from
	 * @param float  $to
	 *
	 * @return string
	 */
	public static function format_price_range( $price, $from, $to ) {
		return sprintf( '%s: %s', __( 'From', 'orderable' ), wc_price( $from ) );
	}

	/**
	 * Get products, sorted by category.
	 *
	 * @param array $args
	 *
	 * @return array
	 */
	public static function get_products_by_category( $args = array() ) {
		// Disable this filter as we don't want to disable categories during our own calls to them.
		remove_filter( 'get_terms_args', array( __CLASS__, 'remove_hidden_categories_from_terms_query' ), 10 );

		$categories     = ! empty( $args['categories'] ) ? $args['categories'] : array();
		$categories     = is_string( $categories ) ? array_filter( explode( ',', $categories ) ) : $categories;
		$has_categories = ! empty( $categories );

		$categories = self::order_categories_by_menu_order( $categories );

		$orderby = ! defined( 'ORDERABLE_PRO_VERSION' ) || empty( $_GET['order_by'] ) ? '' : sanitize_text_field( wp_unslash( $_GET['order_by'] ) ); // phpcs:ignore WordPress.Security.NonceVerification

		if ( empty( $orderby ) ) {
			$orderby = ! defined( 'ORDERABLE_PRO_VERSION' ) || empty( $args['sort'] ) ? 'menu_order' : $args['sort'];
		}

		$products = array();

		/**
		 * Filter description.
		 *
		 * @since 1.0.0
		 * @hook orderable_flatten_products_by_category_level
		 * @param string $flatten_level The flatten level. Default: `all`.
		 * @param array  $args          The layout args.
		 * @return string New value
		 */
		$flatten_level = apply_filters( 'orderable_flatten_products_by_category_level', 'all', $args );

		if ( 'all' === $flatten_level ) {
			$products[] = array(
				'category' => array(),
				'products' => array(),
			);

			$categories_slug = array_filter(
				array_map(
					function( $category_id ) {
						$category = get_term_by( 'id', $category_id, 'product_cat' );

						return empty( $category->slug ) ? false : $category->slug;
					},
					$categories
				)
			);

			$products[0]['products'] = self::get_products(
				array(
					'category' => $categories_slug,
					'orderby'  => $orderby,
					'limit'    => 500,
				)
			);

			/**
			 * Filter the products sorted by categories to be shown in the product layout.
			 *
			 * @since 1.0.0
			 * @hook orderable_get_products_by_category
			 * @param  array $products      The products.
			 * @param  array $args          The args to retrieve the products.
			 * @param  string $flatten_level The flatten level e.g. `all` and `children`.
			 * @return array New value
			 */
			return apply_filters( 'orderable_get_products_by_category', $products, $args, $flatten_level );
		}

		if ( $has_categories ) {
			foreach ( $categories as $category_id ) {
				$category = get_term( $category_id, 'product_cat' );

				if ( is_wp_error( $category ) || empty( $category ) ) {
					continue;
				}

				$products[ $category_id ] = array(
					'category' => array(
						'name'        => $category->name,
						'slug'        => $category->slug,
						'description' => $category->description,
						'depth'       => 0,
						'children'    => null,
					),
					'products' => array(),
				);

				$children_categories = get_terms(
					array(
						'taxonomy'   => 'product_cat',
						'orderby'    => 'menu_order',
						'hide_empty' => true,
						'parent'     => $category->term_id,
					)
				);

				if ( ! empty( $children_categories ) ) {
					$products[ $category_id ]['category']['children'] = array();

					foreach ( $children_categories as $child_category ) {
						$category_products = self::get_products(
							array(
								'limit'    => 500,
								'category' => array( $child_category->slug ),
								'orderby'  => $orderby,
							)
						);

						if ( empty( $category_products ) ) {
							continue;
						}

						if ( 'children' === $flatten_level ) {
							$products_in_category                 = empty( $products[ $category_id ]['products'] ) ? array() : $products[ $category_id ]['products'];
							$products[ $category_id ]['products'] = array_merge( $products_in_category, $category_products );

							continue;
						}

						$products[ $category_id ]['category']['children'][ $child_category->term_id ] = array(
							'category' => array(
								'name'        => $child_category->name,
								'description' => $child_category->description,
								'depth'       => 1,
								'parent'      => $category_id,
							),
							'products' => $category_products,
						);
					}
				} else {
					$category_products = self::get_products(
						array(
							'limit'    => 500,
							'category' => array( $category->slug ),
							'orderby'  => $orderby,
						)
					);

					if ( ! empty( $category_products ) ) {
						$products[ $category_id ]['products'] = $category_products;

						// Add parent attribute if parent is a root category.
						if ( in_array( $category->parent, $categories, true ) ) {
							$products[ $category_id ]['category']['parent'] = $category->parent;
						}
					}
				}
			}
		} else {
			$category_products = self::get_products(
				array(
					'limit'   => 500,
					'orderby' => $orderby,
				)
			);

			if ( ! empty( $category_products ) ) {
				$products[] = array(
					'category' => null,
					'products' => $category_products,
				);
			}
		}

		// Remove categories with no products.
		foreach ( $products as $key => $product_collection ) {
			if ( ! empty( $product_collection['products'] ) || ! empty( $product_collection['category']['children'] ) ) {
				continue;
			}

			unset( $products[ $key ] );
		}

		// Turn this back on to re-disable hidden categories.
		add_filter( 'get_terms_args', array( __CLASS__, 'remove_hidden_categories_from_terms_query' ), 10, 2 );

		$products = self::maybe_flatten_products_by_category( array_filter( $products ), $args );

		// phpcs:ignore WooCommerce.Commenting.CommentHooks
		return apply_filters( 'orderable_get_products_by_category', $products, $args, $flatten_level );
	}

	/**
	 * Get Products.
	 *
	 * A wrapper around the WooCommerce `wc_get_products` and `wc_products_array_orderby` functions.
	 *
	 * @param array $args Arguments.
	 *
	 * @return array
	 */
	public static function get_products( $args ) {
		$args['status'] = 'publish'; // Ensure only published products are returned.

		if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
			$args['stock_status'] = 'instock';
		}

		/**
		 * Filter arguments used to retrieve products from database
		 *
		 * @param array $args WC_Product_Query arguments
		 *
		 * @return array New query arguments
		 * @since 1.2.1
		 * @hook  orderable_get_products_args
		 */
		$products = wc_get_products( apply_filters( 'orderable_get_products_args', $args ) );

		if ( ! empty( $products ) ) {
			$orderby = empty( $args['orderby'] ) ? 'menu_order' : $args['orderby'];

			$order   = 'price-desc' === $orderby ? 'desc' : 'asc';
			$orderby = 'price-desc' === $orderby ? 'price' : $orderby;

			$products = wc_products_array_orderby( $products, $orderby, $order );
		}

		return apply_filters( 'orderable_get_products', $products, $args );
	}

	/**
	 * Order Categories by menu order
	 *
	 * @param array $categories Categories.
	 *
	 * @return array
	 */
	public static function order_categories_by_menu_order( $categories ) {
		$categories_ordered = array();
		$category_terms     = get_terms(
			array(
				'taxonomy'   => 'product_cat',
				'orderby'    => 'menu_order',
				'hide_empty' => true,
			)
		);

		foreach ( $category_terms as $term ) {
			if ( ! in_array( $term->term_id, $categories, true ) ) {
				continue;
			}

			$categories_ordered[] = $term->term_id;
		}

		return $categories_ordered;
	}

	/**
	 * Maybe flatten products by category.
	 *
	 * @param array $products
	 * @param array $args
	 *
	 * @return array
	 */
	public static function maybe_flatten_products_by_category( $products, $args = array() ) {
		// If we're already listing all products with no categories, escape.
		if ( empty( $products ) || isset( $products[0] ) ) {
			return $products;
		}

		$flatten_level = apply_filters( 'orderable_flatten_products_by_category_level', 'all', $args );

		// Don't flatten if set to "none".
		if ( 'none' === $flatten_level ) {
			return $products;
		}

		$flattened_products = array();

		if ( 'children' === $flatten_level ) {
			$orderby = empty( $args['sort'] ) ? 'menu_order' : $args['sort'];

			$order   = 'price-desc' === $orderby ? 'desc' : 'asc';
			$orderby = 'price-desc' === $orderby ? 'price' : $orderby;

			foreach ( $products as $category_id => $product_group ) {
				$product_group['products'] = wc_products_array_orderby( $product_group['products'], $orderby, $order );

				$flattened_products[ $category_id ] = $product_group;
			}
		} else {
			$flattened_products[] = array(
				'category' => null,
				'products' => array(),
			);

			foreach ( $products as $product_group ) {
				if ( ! empty( $product_group['products'] ) ) {
					// Add category products to list.
					$flattened_products[0]['products'] = array_merge( $flattened_products[0]['products'], $product_group['products'] );

					continue;
				} elseif ( ! empty( $product_group['category']['children'] ) ) {
					foreach ( $product_group['category']['children'] as $child_product_group ) {
						// Add category products to list.
						$flattened_products[0]['products'] = array_merge( $flattened_products[0]['products'], $child_product_group['products'] );

						continue;
					}
				}
			}
		}

		return $flattened_products;
	}

	/**
	 * Get add to cart button.
	 *
	 * @param WC_Product $product         Product.
	 * @param string     $classes         Button classes.
	 * @param array      $layout_settings The product layout settings.
	 *
	 * @return string
	 */
	public static function get_add_to_cart_button( $product, $classes = '', $layout_settings = array() ) {
		global $orderable_single_product;

		$args = array(
			'trigger'              => self::get_add_to_cart_trigger( $product ),
			'product_id'           => $product->get_id(),
			'product_type'         => $product->get_type(),
			'variation_id'         => null,
			'variation_attributes' => array(),
			'text'                 => __( 'Add', 'orderable' ),
			'classes'              => $classes,
		);

		if ( Orderable_Helpers::is_variable_product( $args['product_type'] ) ) {
			$args['trigger'] = 'product-options';
			$args['text']    = empty( $orderable_single_product ) ? __( 'Select', 'orderable' ) : $args['text'];
		} elseif ( 'variation' === $args['product_type'] ) {
			$args['product_id']   = $product->get_parent_id();
			$args['variation_id'] = $product->get_id();
		}

		if ( ! $product->is_in_stock() ) {
			$args['classes'] .= ' orderable-button--out-of-stock';
			$args['text']     = __( 'Out of Stock', 'orderable' );
		}

		if ( Orderable_Helpers::is_product_in_the_cart( $product->get_id() ) ) {
			$args['classes'] .= ' orderable-button--product-in-the-cart';
		}

		$product_quantity = Orderable_Helpers::get_product_quantity_in_the_cart( $product->get_id() );

		/**
		 * Filter the Add to Cart button args.
		 *
		 * @since 1.0.0
		 * @hook orderable_add_to_cart_button_args
		 * @param  array      $args            The button args.
		 * @param  WC_Product $product         The product.
		 * @param  array      $layout_settings The product layout settings.
		 * @return array New value
		 */
		$args = apply_filters( 'orderable_add_to_cart_button_args', $args, $product, $layout_settings );

		$counter_element_classes = 'orderable-product__actions-counter';

		return sprintf(
			'<button 
				class="orderable-button %1$s"
				data-orderable-trigger="%2$s"
				data-orderable-product-id="%3$d"
				data-orderable-product-type="%4$s"
				data-orderable-variation-id="%5$d"
				data-orderable-variation-attributes=""
				data-quantity="1"
				data-product_id="%3$d"
				data-product_sku="%6$s"
				data-product_name="%7$s"
				data-price="%8$s"
			>
				%9$s
				<span class="%10$s" data-orderable-product-quantity="%11$d">%11$d</span>
			</button>',
			esc_attr( $args['classes'] ),
			esc_attr( $args['trigger'] ),
			esc_attr( $args['product_id'] ),
			esc_attr( $args['product_type'] ),
			esc_attr( $args['variation_id'] ),
			esc_attr( $product->get_sku() ),
			esc_attr( $product->get_name() ),
			esc_attr( $product->get_price() ),
			wp_kses_post( $args['text'] ),
			$counter_element_classes,
			$product_quantity
		);
	}

	/**
	 * Get update cart item button.
	 *
	 * @param string     $cart_item_key The cart item key.
	 * @param WC_Product $product The product to be edited.
	 * @param string     $classes Button classes.
	 *
	 * @return string
	 */
	public static function get_update_cart_item_button( $cart_item_key, $product, $classes = '' ) {
		$cart_item = WC()->cart->get_cart_item( $cart_item_key );

		$args = array(
			'trigger'              => 'update-cart-item',
			'product_id'           => $cart_item['product_id'],
			'cart_item_key'        => $cart_item_key,
			'variation_id'         => $cart_item['variation_id'],
			'variation_attributes' => empty( array_filter( $cart_item['variation'] ) ) ? false : wp_json_encode( $cart_item['variation'] ),
			'product_type'         => $product->get_type(),
			'text'                 => __( 'Update', 'orderable' ),
			'classes'              => $classes,
		);

		/**
		 * Filter arguments used to the update cart item button.
		 *
		 * @param array $args The arguments.
		 * @param string $cart_item_key The cart item key.
		 *
		 * @return array New arguments
		 * @since 1.4.0
		 * @hook  orderable_update_cart_item_button_args
		 */
		$args = apply_filters( 'orderable_update_cart_item_button_args', $args, $cart_item_key );

		ob_start();
		?>
		<button
			class="orderable-button orderable-product__cancel-update"
			data-orderable-trigger="show-cart"
		>
			<?php echo esc_html__( 'Cancel', 'orderable' ); ?>
		</button>
		<button
			class="orderable-button <?php echo esc_attr( $args['classes'] ); ?>"
			data-orderable-trigger="<?php echo esc_attr( $args['trigger'] ); ?>"
			data-orderable-cart-item-key="<?php echo esc_attr( $args['cart_item_key'] ); ?>"
			data-orderable-product-id="<?php echo esc_attr( $args['product_id'] ); ?>"
			data-orderable-variation-id="<?php echo esc_attr( $args['variation_id'] ); ?>"
			data-orderable-variation-attributes="<?php echo esc_attr( $args['variation_attributes'] ); ?>"
			data-orderable-product-type="<?php echo esc_attr( $args['product_type'] ); ?>"
		>
			<?php echo esc_html( $args['text'] ); ?>
		</button>
		<?php

		/**
		 * Filter the Update Cart Item button HTML.
		 *
		 * @param string|false $update_cart_item_button_html The Update Cart Item button HTML.
		 *
		 * @return string|false New HTML
		 * @since 1.4.0
		 * @hook  orderable_update_cart_item_button_html
		 */
		$html = apply_filters( 'orderable_update_cart_item_button_html', ob_get_clean() );

		return $html;
	}

	/**
	 * Get add to cart trigger value.
	 *
	 * @param WC_Product $product
	 *
	 * @return string
	 */
	public static function get_add_to_cart_trigger( $product ) {
		$trigger = $product->is_type( 'variable' ) ? 'product-options' : 'add-to-cart';

		return apply_filters( 'orderable_get_add_to_cart_trigger', $trigger, $product );
	}

	/**
	 * Get a list of attributes for available variations.
	 *
	 * @param WC_Product_Variable $product
	 *
	 * @return array
	 */
	public static function get_available_variation_attributes( $product ) {
		$available_variation_attributes = array();
		$available_variations           = $product->get_available_variations();
		$available_variations           = wc_list_pluck( $available_variations, 'attributes' );

		if ( empty( $available_variations ) ) {
			return $available_variation_attributes;
		}

		foreach ( $available_variations as $available_variation ) {
			foreach ( $available_variation as $attribute_slug => $attribute_value ) {
				if ( ! isset( $available_variation_attributes[ $attribute_slug ] ) ) {
					$available_variation_attributes[ $attribute_slug ] = array();
				}

				$available_variation_attributes[ $attribute_slug ][] = $attribute_value;
			}
		}

		return $available_variation_attributes;
	}

	/**
	 * Get available attributes.
	 *
	 * This method remove an attributes which don't belong to
	 * an active variation.
	 *
	 * @param WC_Product_Variable $product
	 *
	 * @return array
	 */
	public static function get_available_attributes( $product ) {
		$available_attributes           = array();
		$attributes                     = $product->get_variation_attributes();
		$available_variation_attributes = self::get_available_variation_attributes( $product );

		if ( empty( $attributes ) ) {
			return $available_attributes;
		}

		foreach ( $attributes as $attribute_name => $attribute_terms ) {
			$attribute_name_sanitized = wc_variation_attribute_name( $attribute_name );

			if ( empty( $available_variation_attributes[ $attribute_name_sanitized ] ) ) {
				continue;
			}

			if ( ! isset( $available_attributes[ $attribute_name ] ) ) {
				$available_attributes[ $attribute_name ] = array();
			}

			foreach ( $attribute_terms as $attribute_term ) {
				if ( ! in_array( $attribute_term, $available_variation_attributes[ $attribute_name_sanitized ], true ) && ! in_array( '', $available_variation_attributes[ $attribute_name_sanitized ], true ) ) {
					continue;
				}

				$available_attributes[ $attribute_name ][] = $attribute_term;
			}
		}

		return $available_attributes;
	}

	/**
	 * Is product single page hidden?
	 *
	 * @param int|WC_Product $product
	 *
	 * @return bool
	 */
	public static function is_product_hidden( $product ) {
		if ( is_numeric( $product ) ) {
			$product = wc_get_product( $product );
		}

		if ( empty( $product ) ) {
			return false;
		}

		$categories = $product->get_category_ids();

		if ( empty( $categories ) ) {
			return false;
		}

		foreach ( $categories as $category_id ) {
			if ( self::is_category_hidden( $category_id ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Is this category hidden?
	 *
	 * @param $category_id
	 *
	 * @return bool
	 */
	public static function is_category_hidden( $category_id ) {
		$hidden_categories = Orderable_Settings::get_hidden_categories();

		return in_array( $category_id, $hidden_categories, true );
	}

	/**
	 * Set if product is visible, based on category settings.
	 *
	 * @param bool $visible
	 * @param int  $product_id
	 *
	 * @return bool
	 */
	public static function set_product_visibility( $visible, $product_id ) {
		$hidden = self::is_product_hidden( $product_id );

		return $hidden ? false : $visible;
	}

	/**
	 * Disable cart permalink.
	 *
	 * @param string $permalink
	 *
	 * @return bool
	 */
	public static function disable_cart_link( $permalink, $cart_item, $cart_item_key ) {
		if ( ! isset( $cart_item['data'] ) ) {
			return $permalink;
		}

		return self::is_product_hidden( $cart_item['data'] ) ? false : $permalink;
	}

	/**
	 * Set product page to 404 if required.
	 */
	public static function products_404() {
		if ( is_admin() || ! ( is_product() && is_single() ) ) {
			return;
		}

		global $post;

		if ( empty( $post ) ) {
			return;
		}

		$product = wc_get_product( $post->ID );

		if ( empty( $product ) || ! self::is_product_hidden( $product ) ) {
			return;
		}

		global $wp_query;

		$wp_query->set_404();
		status_header( 404 );
		nocache_headers();
	}

	/**
	 * Exclude hidden categories from queries.
	 *
	 * @param $tax_query
	 * @param $wc_query
	 *
	 * @return array
	 */
	public static function remove_hidden_categories_from_products_query( $tax_query, $wc_query ) {
		$hidden_categories = Orderable_Settings::get_hidden_categories();

		if ( empty( $hidden_categories ) ) {
			return $tax_query;
		}

		$tax_query[] = array(
			'taxonomy' => 'product_cat',
			'field'    => 'term_id',
			'terms'    => $hidden_categories,
			'operator' => 'NOT IN',
		);

		return $tax_query;
	}

	/**
	 * Remove hidden categories from terms query.
	 *
	 * As well as hiding from terms lists (sidebar widgets, etc),
	 * this also disables the archive page.
	 *
	 * @param array $args       Args.
	 * @param array $taxonomies Taxonomies.
	 *
	 * @return mixed
	 */
	public static function remove_hidden_categories_from_terms_query( $args, $taxonomies ) {
		if ( is_admin() ) {
			return $args;
		}

		$taxonomy = ! empty( $args['taxonomy'] ) && isset( $args['taxonomy'][0] ) ? $args['taxonomy'][0] : false;

		if ( 'product_cat' !== $taxonomy || is_admin() ) {
			return $args;
		}

		// Exclude hidden categories.
		$hidden_categories = Orderable_Settings::get_hidden_categories();
		$args['exclude']   = ! is_array( $args['exclude'] ) ? array() : $args['exclude'];
		$args['exclude']   = array_merge( $args['exclude'], $hidden_categories );

		return $args;
	}

	/**
	 * Remove hidden products from sitemap.
	 *
	 * @param array  $query_args Query args.
	 * @param string $post_type  Post type.
	 *
	 * @return mixed
	 */
	public static function remove_hidden_products_from_sitemap( $query_args, $post_type ) {
		if ( 'product' !== $post_type ) {
			return $query_args;
		}

		$hidden_categories = Orderable_Settings::get_hidden_categories();

		if ( empty( $hidden_categories ) ) {
			return $query_args;
		}

		$tax_query = array(
			'taxonomy'         => 'product_cat',
			'terms'            => $hidden_categories,
			'field'            => 'term_id',
			'include_children' => true,
			'operator'         => 'NOT IN',
		);

		if ( ! isset( $query_args['tax_query'] ) ) {
			$query_args['tax_query'] = array();
		}

		$query_args['tax_query'][] = $tax_query;

		return $query_args;
	}

	/**
	 * Exclude hidden categories from sitemap.
	 *
	 * @param array  $query_args Query args.
	 * @param string $taxonomy   Taxonomy.
	 *
	 * @return mixed
	 */
	public static function remove_hidden_categories_from_sitemap( $query_args, $taxonomy ) {
		if ( 'product_cat' !== $taxonomy ) {
			return $query_args;
		}

		$hidden_categories = Orderable_Settings::get_hidden_categories();

		if ( empty( $hidden_categories ) ) {
			return $query_args;
		}

		if ( ! isset( $query_args['exclude'] ) ) {
			$query_args['exclude'] = $hidden_categories;
		} else {
			if ( is_array( $hidden_categories ) ) {
				$query_args['exclude'] = array_merge( $query_args['exclude'], $hidden_categories );
			} else {
				$query_args['exclude'] = $query_args['exclude'] . ',' . implode( ',', $hidden_categories );
			}
		}

		return $query_args;
	}

	/**
	 * Get product accordion data.
	 *
	 * @param WC_Product $product Product.
	 *
	 * @return array
	 */
	public static function get_accordion_data( $product ) {
		$data = [];

		$description = Orderable_Settings::get_setting( 'drawer_quickview_description' );

		if ( 'none' === $description ) {
			// phpcs:ignore WooCommerce.Commenting.CommentHooks
			return apply_filters( 'orderable_get_accordion_data', $data, $product );
		}

		$description = 'short' === $description ? $product->get_short_description() : $product->get_description();

		// phpcs:ignore WooCommerce.Commenting.CommentHooks
		$content = apply_filters( 'the_content', $description );

		if ( empty( $content ) ) {
			// phpcs:ignore WooCommerce.Commenting.CommentHooks
			return apply_filters( 'orderable_get_accordion_data', $data, $product );
		}

		$data[] = array(
			'title'   => __( 'Description', 'orderable' ),
			'content' => $content,
			'id'      => 'accordion-description',
		);

		/**
		 * Filter product accordion data.
		 *
		 * @var array      $data
		 * @var WC_Product $product
		 * @since 1.0.0
		 */
		return apply_filters( 'orderable_get_accordion_data', $data, $product );
	}

	/**
	 * Update the button args to allow adding to the cart without opening side drawer.
	 *
	 * @param array      $args The button args.
	 * @param WC_Product $product The product.
	 * @param array      $layout_settings The product layout settings.
	 * @return array
	 */
	public static function update_button_args_to_allow_add_to_cart_without_side_drawer( $args, $product, $layout_settings ) {
		if ( empty( $layout_settings['quantity_roller'] ) ) {
			return $args;
		}

		if ( 'add-to-cart' !== $args['trigger'] ) {
			return $args;
		}

		$args['trigger'] = 'add-to-cart-without-side-drawer';

		return $args;
	}

	/**
	 * Update the quantity roller fragments.
	 *
	 * @param array $fragments The WooCommerce cart fragments.
	 * @return array
	 */
	public static function handle_adding_product_without_side_drawer( $fragments ) {
		// phpcs:ignore WordPress.Security.NonceVerification
		if ( empty( $_POST['action'] ) || empty( $_POST['product_id'] ) ) {
			return $fragments;
		}

		$action     = sanitize_text_field( wp_unslash( $_POST['action'] ) ); // phpcs:ignore WordPress.Security.NonceVerification
		$product_id = absint( sanitize_text_field( wp_unslash( $_POST['product_id'] ) ) ); // phpcs:ignore WordPress.Security.NonceVerification

		switch ( $action ) {
			case 'orderable_add_to_cart':
				$cart_item = false;
				foreach ( WC()->cart->get_cart() as $cart_item_data ) {
					if ( $product_id !== $cart_item_data['product_id'] ) {
						continue;
					}

					$cart_item = $cart_item_data;
				}

				if ( empty( $cart_item ) ) {
					return $fragments;
				}

				ob_start();
				self::get_quantity_roller( $cart_item );
				$fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-quantity-roller" ] = ob_get_clean();

				$fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );

				break;

			case 'orderable_cart_quantity':
				// phpcs:ignore WordPress.Security.NonceVerification
				if ( empty( $_POST['cart_item_key'] ) || ! isset( $_POST['quantity'] ) ) {
					return $fragments;
				}

				$cart_item_key = sanitize_text_field( wp_unslash( $_POST['cart_item_key'] ) ); // phpcs:ignore WordPress.Security.NonceVerification

				if ( empty( $cart_item_key ) ) {
					return $fragments;
				}

				$cart_item = WC()->cart->get_cart_item( $cart_item_key );

				ob_start();
				self::get_quantity_roller( $cart_item );
				$fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-quantity-roller" ] = ob_get_clean();

				if ( empty( $cart_item ) ) {
					$fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );

					return $fragments;
				}

				$fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );

				break;

			default:
				break;
		}

		return $fragments;
	}

	/**
	 * Get the HTML markup for the quantity roller element.
	 *
	 * @param array  $cart_item      The cart item.
	 * @param string $product_price  The product price.
	 * @return void
	 */
	public static function get_quantity_roller( $cart_item, $product_price = '' ) {
		if ( empty( $cart_item ) ) {
			?>
			<div class="orderable-quantity-roller"></div>
			<?php
		} else {
			?>
				<div class="orderable-quantity-roller orderable-quantity-roller--is-active">
						<span class="orderable-quantity-roller__roller">
							<button 
								class="orderable-quantity-roller__button orderable-quantity-roller__button--decrease"
								data-orderable-trigger="decrease-quantity"
								data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
								data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
								data-orderable-quantity="<?php echo esc_attr( $cart_item['quantity'] ); ?>"
							><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>
							<span
								class="orderable-quantity-roller__quantity"
								contenteditable="true"
								inputmode="numeric"
								data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
								data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
							>
								<?php echo esc_attr( $cart_item['quantity'] ); ?>
							</span>
							<button
								class="orderable-quantity-roller__button orderable-quantity-roller__button--increase"
								data-orderable-trigger="increase-quantity"
								data-orderable-cart-item-key="<?php echo esc_attr( $cart_item['key'] ); ?>"
								data-orderable-product-id="<?php echo esc_attr( $cart_item['product_id'] ); ?>"
								data-orderable-quantity="<?php echo esc_attr( $cart_item['quantity'] ); ?>"
							>+</button>
						</span>
						<?php if ( ! empty( $product_price ) ) : ?>
							<span class="orderable-quantity-roller__price"><?php echo wp_kses_post( $product_price ); ?></span>
						<?php endif; ?>
					</div>
			<?php
		}
	}

	/**
	 * Get the HTML markup for the product counter element.
	 *
	 * @param  int $product_id The product ID.
	 * @return string The HTML markup.
	 */
	public static function get_product_counter( $product_id ) {
		$quantity = Orderable_Helpers::get_product_quantity_in_the_cart( absint( $product_id ) );

		?>
			<span
				class="orderable-product__actions-counter"
				data-orderable-product-quantity="<?php echo esc_attr( $quantity ); ?>"
				style="animation: wobble-hor-bottom .8s both;"
			>
				<?php echo esc_html( $quantity ); ?>
			</span>

		<?php

		$html_markup = ob_get_clean();

		/**
		 * Filter the product counter HTML markup.
		 *
		 * @param string $html_markup The HTML markup.
		 *
		 * @return string New value.
		 * @since 1.7.0
		 * @hook  orderable_product_counter_html
		 */
		return apply_filters( 'orderable_product_counter_html', $html_markup );
	}

	/**
	 * Update product counter fragments for removed item.
	 *
	 * @param string  $cart_item_key The removed cart item key.
	 * @param WC_Cart $wc_cart       The WC Cart.
	 * @return void
	 */
	public static function update_product_counter_fragments_for_removed_item( $cart_item_key, $wc_cart ) {
		$product_id = empty( $wc_cart->removed_cart_contents[ $cart_item_key ]['product_id'] ) ? false : $wc_cart->removed_cart_contents[ $cart_item_key ]['product_id'];

		if ( ! $product_id ) {
			return;
		}

		add_filter(
			'woocommerce_add_to_cart_fragments',
			function( $fragments ) use ( $product_id ) {
				$fragments[ ".orderable-product[data-orderable-product-id='{$product_id}'] .orderable-product__actions-button .orderable-product__actions-counter" ] = self::get_product_counter( $product_id );

				return $fragments;
			}
		);
	}

	/**
	 * Add `.orderable-main--quantity-roller` class to the `.orderable-main` element.
	 *
	 * @param string $class The class attribute value.
	 * @param array  $args  The layout settings.
	 * @return string
	 */
	public static function add_quantity_roller_class( $class, $args ) {
		if ( empty( $args['quantity_roller'] ) ) {
			return $class;
		}

		$class .= ' orderable-main--quantity-roller';

		return $class;
	}
}