Change quantity input field to a Dropdown in WooCommerce - php

I'm using the following in function.php and it works great on a single product page. The issue I have is on the cart page when you choose a different quantity it doesn't automatically update the cart. Any ideas?
function woocommerce_quantity_input( $args = array(), $product = null, $echo = true ) {
if ( is_null( $product ) ) {
$product = $GLOBALS['product'];
}
$defaults = array(
'input_id' => uniqid( 'quantity_' ),
'input_name' => 'quantity',
'input_value' => '1',
'classes' => apply_filters( 'woocommerce_quantity_input_classes', array( 'input-text', 'qty', 'text' ), $product ),
'max_value' => apply_filters( 'woocommerce_quantity_input_max', -1, $product ),
'min_value' => apply_filters( 'woocommerce_quantity_input_min', 0, $product ),
'step' => apply_filters( 'woocommerce_quantity_input_step', 1, $product ),
'pattern' => apply_filters( 'woocommerce_quantity_input_pattern', has_filter( 'woocommerce_stock_amount', 'intval' ) ? '[0-9]*' : '' ),
'inputmode' => apply_filters( 'woocommerce_quantity_input_inputmode', has_filter( 'woocommerce_stock_amount', 'intval' ) ? 'numeric' : '' ),
'product_name' => $product ? $product->get_title() : '',
);
$args = apply_filters( 'woocommerce_quantity_input_args', wp_parse_args( $args, $defaults ), $product );
// Apply sanity to min/max args - min cannot be lower than 0.
$args['min_value'] = max( $args['min_value'], 0 );
// Change 6 to max quantity
$args['max_value'] = 0 < $args['max_value'] ? $args['max_value'] : 6;
// Max cannot be lower than min if defined.
if ( '' !== $args['max_value'] && $args['max_value'] < $args['min_value'] ) {
$args['max_value'] = $args['min_value'];
}
$options = '';
for ( $count = $args['min_value']; $count <= $args['max_value']; $count = $count + $args['step'] ) {
// Cart item quantity defined?
if ( '' !== $args['input_value'] && $args['input_value'] >= 1 && $count == $args['input_value'] ) {
$selected = 'selected';
} else $selected = '';
$options .= '<option value="' . $count . '"' . $selected . '>' . $count . '</option>';
}
$string = '<div class="quantity"><span>Qty</span><select name="' . $args['input_name'] . '">' . $options . '</select></div>';
if ( $echo ) {
echo $string;
} else {
return $string;
}
}

Caution: First you should never overwrite WooCommerce core files, for many reasons. So it's prohibited.
Instead as woocommerce_quantity_input() function call the template file global/quantity-input.php, you can override that template via your child theme.
To understand how to override templates, read carefully: Overriding templates via a theme in WooCommerce.
Now, remove all your related quantity changes and code from you web site (restore everything as before).
Then copy quantity-input.php file located inside WooCommerce plugin > templates > global, to your child theme into a "woocommerce" folder > "global" subfolder.
Once done, open / edit it, and replace the template content with:
<?php
/**
* Product quantity inputs
*
* This template can be overridden by copying it to yourtheme/woocommerce/global/quantity-input.php.
*
* HOWEVER, on occasion WooCommerce will need to update template files and you
* (the theme developer) will need to copy the new files to your theme to
* maintain compatibility. We try to do this as little as possible, but it does
* happen. When this occurs the version of the template file will be bumped and
* the readme will list any important changes.
*
* #see https://docs.woocommerce.com/document/template-structure/
* #package WooCommerce\Templates
* #version 4.0.0
*/
defined( 'ABSPATH' ) || exit;
if ( $max_value && $min_value === $max_value ) {
?>
<div class="quantity hidden">
<input type="hidden" id="<?php echo esc_attr( $input_id ); ?>" class="qty" name="<?php echo esc_attr( $input_name ); ?>" value="<?php echo esc_attr( $min_value ); ?>" />
</div>
<?php
} else {
/* translators: %s: Quantity. */
$label = ! empty( $args['product_name'] ) ? sprintf( esc_html__( '%s quantity', 'woocommerce' ), wp_strip_all_tags( $args['product_name'] ) ) : esc_html__( 'Quantity', 'woocommerce' );
?>
<div class="quantity">
<?php do_action( 'woocommerce_before_quantity_input_field' ); ?>
<label class="screen-reader-text" for="<?php echo esc_attr( $input_id ); ?>"><?php echo esc_attr( $label ); ?></label>
<?php
if ( is_cart() ) :
?>
<input
type="hidden"
id="<?php echo esc_attr( $input_id ); ?>"
class="<?php echo esc_attr( join( ' ', (array) $classes ) ); ?>"
name="<?php echo esc_attr( $input_name ); ?>"
value="<?php echo esc_attr( $input_value ); ?>"
title="<?php echo esc_attr_x( 'Qty', 'Product quantity input tooltip', 'woocommerce' );
?>" />
<?php
endif;
$options = ''; // Initializing
for ( $i = $min_value; $i <= $max_value; $i += $step ) :
$selected = ( '' !== $input_value && $input_value >= 1 && $i == $input_value ) ? 'selected' : '';
$options .= '<option value="' . $i . '"' . $selected . '>' . $i . '</option>';
endfor;
// Change input name on select field
$attr_name = is_cart() ? 'data-name' : 'name';
?>
<select <?php echo $attr_name; ?>="<?php echo $input_name; ?>"><?php echo $options; ?></select>
<?php do_action( 'woocommerce_after_quantity_input_field' ); ?>
</div>
<?php
}
Now some jQuery code is required, to make things work on cart page.
// jQuery - cart jQuery script for quantity dropdown
add_action( 'woocommerce_after_cart', 'cart_quantity_dropdown_js' );
function cart_quantity_dropdown_js() {
?>
<script type="text/javascript">
jQuery( function($){
$(document.body).on('change blur', 'form.woocommerce-cart-form .quantity select', function(e){
var t = $(this), q = t.val(), p = t.parent();
$(this).parent().find('input').val($(this).val());
console.log($(this).parent().find('input').val());
});
});
</script>
<?php
}
This code goes in functions.php file of the active child theme (or active theme).
Now to restrict the max quantity to 6, add the following code:
// Restricting product max quantity to 6
add_filter( 'woocommerce_quantity_input_args', 'woocommerce_quantity_input_args_callback', 10, 2 );
function woocommerce_quantity_input_args_callback( $args, $product ) {
$args['max_value'] = 6;
return $args;
}
// Restricting product variation max quantity to 6
add_filter( 'woocommerce_available_variation', 'filter_woocommerce_available_variation', 10, 3);
function filter_woocommerce_available_variation( $data, $product, $variation ) {
$data['max_qty'] = 6;
return $data;
}
This code goes in functions.php file of the active child theme (or active theme).
Now it works everywhere (tested on last WooCommerce version under Storefront theme).

The default WooCommerce Add to Cart “Quantity Input” is a simple input field where you can enter the number of items or click on the “+” and “-” to increase/reduce the quantity.
To let the users choose the quantity from a drop-down instead of having to manually input the number. This can be done using woocommerce_quantity_input function. Simply add the following Snippet to your Functions.php.
function woocommerce_quantity_input( $args = array(), $product = null, $echo = true ) {
if ( is_null( $product ) ) {
$product = $GLOBALS['product'];
}
$defaults = array(
'input_id' => uniqid( 'quantity_' ),
'input_name' => 'quantity',
'input_value' => '1',
'classes' => apply_filters( 'woocommerce_quantity_input_classes', array( 'input-text', 'qty', 'text' ), $product ),
'max_value' => apply_filters( 'woocommerce_quantity_input_max', -1, $product ),
'min_value' => apply_filters( 'woocommerce_quantity_input_min', 0, $product ),
'step' => apply_filters( 'woocommerce_quantity_input_step', 1, $product ),
'pattern' => apply_filters( 'woocommerce_quantity_input_pattern', has_filter( 'woocommerce_stock_amount', 'intval' ) ? '[0-9]*' : '' ),
'inputmode' => apply_filters( 'woocommerce_quantity_input_inputmode', has_filter( 'woocommerce_stock_amount', 'intval' ) ? 'numeric' : '' ),
'product_name' => $product ? $product->get_title() : '',
);
$args = apply_filters( 'woocommerce_quantity_input_args', wp_parse_args( $args, $defaults ), $product );
$args['min_value'] = max( $args['min_value'], 0 );
$args['max_value'] = 0 < $args['max_value'] ? $args['max_value'] : 20;
if ( '' !== $args['max_value'] && $args['max_value'] < $args['min_value'] ) {
$args['max_value'] = $args['min_value'];
}
$options = '';
for ( $count = $args['min_value']; $count <= $args['max_value']; $count = $count + $args['step'] ) {
if ( '' !== $args['input_value'] && $args['input_value'] >= 1 && $count == $args['input_value'] ) {
$selected = 'selected';
} else $selected = '';
$options .= '<option value="' . $count . '"' . $selected . '>' . $count . '</option>';
}
$string = '<div class="quantity"><span>Qty</span><select name="' . $args['input_name'] . '">' . $options . '</select></div>';
if ( $echo ) {
echo $string;
} else {
return $string;
}
}

Related

Get product id in "woocommerce_quantity_input" pluggable function

I am using woocommerce_quantity_input pluggable function to modify the quantity input from a text box to a dropdown on my site.
On the cart page, when outputting the quantity input, I need to get the product ID so I can grab an ACF field from the single product page.
My code:
function woocommerce_quantity_input($data = null, $args = array(), $echo = true) {
global $product;
$set_quantity_limit = get_field('set_quantity_limit');
if ( !$data || is_product() ) {
$defaults = array(
'input_id' => '',
'input_name' => 'quantity',
'input_value' => '1',
'max_value' => apply_filters( 'woocommerce_quantity_input_max', '', $product ),
'min_value' => apply_filters( 'woocommerce_quantity_input_min', '', $product ),
'step' => apply_filters( 'woocommerce_quantity_input_step', '1', $product ),
);
} else {
$defaults = array(
'input_id' => $data['input_id'],
'input_name' => $data['input_name'],
'input_value' => $data['input_value'],
'step' => apply_filters( 'cw_woocommerce_quantity_input_step', '1', $product ),
'max_value' => apply_filters( 'cw_woocommerce_quantity_input_max', '', $product ),
'min_value' => apply_filters( 'cw_woocommerce_quantity_input_min', '', $product ),
);
}
if($set_quantity_limit){
if ( ! $product->is_sold_individually() ) {
$min = $defaults['min_value'] = 1;
$max = $defaults['max_value'] = $set_quantity_limit;
$step = $defaults['step'] = 1;
}
} else {
if ( ! empty( $defaults['min_value'] ) )
$min = $defaults['min_value'];
else $min = 1;
if ( ! empty( $defaults['max_value'] ) )
$max = $defaults['max_value'];
else $max = 6;
if ( ! empty( $defaults['step'] ) )
$step = $defaults['step'];
else $step = 1;
}
$options = '';
for ( $count = $min; $count <= $max; $count = $count+$step ) {
$selected = (($count == $defaults['input_value']) ? ' selected' : '');
$suffix_text_with_count = $count . ( ( $count == 6 ) ? ' - 1 Mastercase' : ' box - 12 ct.' );
$options .= '<option value="' . $count . '"'.$selected.'>' . ( ( $set_quantity_limit ) ? $count : $suffix_text_with_count ) . '</option>';
}
$string = '<div class="quantity quantity_select" style="' . $defaults['style'] . '">';
$string .= '<label class="screen-reader-text" for="' . esc_attr( $defaults['input_id'] ) . '">' . _x( 'Quantity', 'woocommerce' ) . '</label>';
$string .= '<select ';
$string .= 'name="' . esc_attr( $defaults['input_name'] ) . '" ';
$string .= 'title="' . _x( 'Qty', 'Product Description', 'woocommerce' ) . '" ';
$string .= 'class="qty">';
$string .= $options;
$string .= '</select>';
$string .= '</div>';
if ( $echo ) {
echo $string;
} else {
return $string;
}
}
This function applies the changes to all quantity inputs, not just those on the shop page, the single product page and the cart page.
Product is passed as as 2nd argument to the woocommerce_quantity_input function.
So use it like this:
function woocommerce_quantity_input( $args = array(), $product = null, $echo = true ) {
if ( is_null( $product ) ) {
$product = $GLOBALS['product'];
}
// Is a WC product
if ( is_a( $product, 'WC_Product' ) ) {
$product_id = $product->get_id();
echo 'Product ID = ' . $product_id;
// etc..
}
}

Metabox with multi checkbox in WooCommerce admin single orders

I added successfully a Metabox with a multi checkbox field that is displayed on admin single order pages and works perfectly.
I am using Multi checkbox fields in Woocommerce backend answer code for that multi checkbox.
// Adding Meta container admin shop_order pages
add_action( 'add_meta_boxes', 'em_add_meta_boxes' );
if ( ! function_exists( 'em_add_meta_boxes' ) )
{
function em_add_meta_boxes()
{
add_meta_box( 'em_other_fields', __('Employee Extra Actions','woocommerce'), 'em_add_other_fields_for_order_empl', 'shop_order', 'side', 'core' );
}
}
// Adding Meta field in the meta container admin shop_order pages
if ( ! function_exists( 'em_add_other_fields_for_order_empl' ) )
{
function em_add_other_fields_for_order_empl()
{
global $post;
echo '<div class="options_group">';
woocommerce_wp_multi_checkbox( array(
'id' => 'employee_actions12',
'name' => 'employee_actions12[]',
'label' => __('Levels', 'woocommerce'),
'options' => array(
'tee' => __( 'MBO', 'woocommerce' ),
'saa' => __( 'HBO', 'woocommerce' ),
'tee1' => __( 'WO', 'woocommerce' ),
)
) );
echo '</div>';
}
}
Final part of code is to save at database, Here is it:
add_action( 'save_post', 'save_product_options_custom_fields32', 30, 1 );
function save_product_options_custom_fields32( $post_id ){
if( isset( $_POST['employee_actions12'] ) ){
$post_data = $_POST['employee_actions12'];
// Multi data sanitization
$sanitize_data = array();
if( is_array($post_data) && sizeof($post_data) > 0 ){
foreach( $post_data as $value ){
$sanitize_data[] = esc_attr( $value );
}
}
update_post_meta( $post_id, 'employee_actions12', $sanitize_data );
}
}
I know code works for product pages with action: 'woocommerce_product_process_meta'
So, i need help for saving at db, an fixing error notice for array (i think this can happen if we select default value).
There was another issue with the function woocommerce_wp_multi_checkbox() that I have updated again (when used in a custom metabox).
I have also revisited all your code, specially the last function that saves the multi-checkboxes selected values.
The complete code:
// WooCommerce admin custom multi checkbox field function
function woocommerce_wp_multi_checkbox( $field ) {
global $thepostid, $post;
if( ! $thepostid ) {
$thepostid = $post->ID;
}
$field['value'] = get_post_meta( $thepostid, $field['id'], true );
$thepostid = empty( $thepostid ) ? $post->ID : $thepostid;
$field['class'] = isset( $field['class'] ) ? $field['class'] : 'select short';
$field['style'] = isset( $field['style'] ) ? $field['style'] : '';
$field['wrapper_class'] = isset( $field['wrapper_class'] ) ? $field['wrapper_class'] : '';
$field['value'] = isset( $field['value'] ) ? $field['value'] : array();
$field['name'] = isset( $field['name'] ) ? $field['name'] : $field['id'];
$field['desc_tip'] = isset( $field['desc_tip'] ) ? $field['desc_tip'] : false;
echo '<fieldset class="form-field ' . esc_attr( $field['id'] ) . '_field ' . esc_attr( $field['wrapper_class'] ) . '">
<legend>' . wp_kses_post( $field['label'] ) . '</legend>';
if ( ! empty( $field['description'] ) && false !== $field['desc_tip'] ) {
echo wc_help_tip( $field['description'] );
}
echo '<ul class="wc-radios">';
foreach ( $field['options'] as $key => $value ) {
echo '<li><label><input
name="' . esc_attr( $field['name'] ) . '"
value="' . esc_attr( $key ) . '"
type="checkbox"
class="' . esc_attr( $field['class'] ) . '"
style="' . esc_attr( $field['style'] ) . '"
' . ( is_array( $field['value'] ) && in_array( $key, $field['value'] ) ? 'checked="checked"' : '' ) . ' /> ' . esc_html( $value ) . '</label>
</li>';
}
echo '</ul>';
if ( ! empty( $field['description'] ) && false === $field['desc_tip'] ) {
echo '<span class="description">' . wp_kses_post( $field['description'] ) . '</span>';
}
echo '</fieldset>';
}
// Adding a custom Metabox on WooCommerce single orders
add_action( 'add_meta_boxes', 'add_custom_shop_order_metabox' );
function add_custom_shop_order_metabox(){
add_meta_box(
'custom_shop_order_metabox',
__('Employee Extra Actions', 'woocommerce'),
'content_custom_shop_order_metabox',
'shop_order',
'side',
'core'
);
}
// Custom Metabox content on WooCommerce single orders
function content_custom_shop_order_metabox() {
global $thepostid, $post;
echo '<div class="options_group">';
woocommerce_wp_multi_checkbox( array(
'id' => 'employee_actions12',
'name' => 'employee_actions12[]',
'label' => __('Levels', 'woocommerce'),
'options' => array(
'tee' => __( 'MBO', 'woocommerce' ),
'saa' => __( 'HBO', 'woocommerce' ),
'tee1' => __( 'WO', 'woocommerce' ),
),
) );
echo '</div>';
}
// Save WooCommerce single orders Custom Metabox field values
add_action( 'save_post_shop_order', 'save_custom_shop_order_metabox_field_values' );
function save_custom_shop_order_metabox_field_values( $post_id ){
if ( ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE )
|| ! current_user_can( 'edit_shop_order', $post_id ) ) {
return;
}
if( isset( $_POST['employee_actions12'] ) ){
update_post_meta( $post_id, 'employee_actions12', wc_clean($_POST['employee_actions12']) );
}
}
Code goes in functions.php file of the active child theme (or active theme). Tested and works.

Getting current active product filter

There is a widget called Active Product Filter in WooCommerce, I couldn't find a way to override it's designed so I tried making my own;
function abc_active_filter(){
$queryData = array();
parse_str($_SERVER['QUERY_STRING'], $queryData);
$active_filter = '';
foreach($queryData as $key => $value){
$active_filter = $active_filter.'<div>CLIK TO REMOVE - <span>'.$key.'</span><div>';
}
if(sizeof($queryData) > 0){
$active_filter = '<h3>Active Filter</h3>'.$active_filter;
}
return $active_filter;
}
add_shortcode('csx_active_filter', 'abc_active_filter');
This will output the query key of the current URL, for example;
min_price,
filter_size
whereas using the widget Active Product Filter the output is;
Min Price $589
Large
is there a way to achieve the output the same as the Active Product Filter? I found this the same question with me but the accepted answer is far different from what I'm trying to achieve and the one who asked the question is only looking for active filters by checking the query key.
Anyway, with the following code, I can achieve the same output of Active Product Filter;
if($key === 'min_price'){
$active_filter = $active_filter.'<div>CLIK TO REMOVE - <span>Min Price '.$value.'</span><div>';
}else if($key === 'filter_size'){
$active_filter = $active_filter.'<div>CLIK TO REMOVE - <span>'.$value.'</span><div>';
}else if .... and so on...
The problem is, filters are dynamic and I don't know all of them and it will take a lot of lines of codes to put them in conditional statements.
You can try using WC_Query::get_layered_nav_chosen_attributes();, simply print its value and see if the result is what you need.
print_r(WC_Query::get_layered_nav_chosen_attributes());
Otherwise, I wouldn't recommend to reinvent the wheel. You can override the widget by extending the class of WC_Widget_Layered_Nav_Filters.
In your functions.php, insert the below code. You can change its layout, elements depending on what design you wanted.
class Your_New_WC_Widget_Layered_Nav_Filters extends WC_Widget_Layered_Nav_Filters {
/**
* Constructor.
*/
public function __construct() {
$this->widget_cssclass = 'woocommerce widget_layered_nav_filters';
$this->widget_description = __( 'Display a list of active product filters.', 'woocommerce' );
$this->widget_id = 'woocommerce_layered_nav_filters';
$this->widget_name = __( 'Active Product Filters', 'woocommerce' );
$this->settings = array(
'title' => array(
'type' => 'text',
'std' => __( 'The Active filters', 'woocommerce' ),
'label' => __( 'Title', 'woocommerce' ),
),
);
parent::__construct();
}
/**
* Output widget.
*
* #see WP_Widget
* #param array $args Arguments.
* #param array $instance Widget instance.
*/
public function widget( $args, $instance ) {
if ( ! is_shop() && ! is_product_taxonomy() ) {
return;
}
$_chosen_attributes = WC_Query::get_layered_nav_chosen_attributes();
$min_price = isset( $_GET['min_price'] ) ? wc_clean( wp_unslash( $_GET['min_price'] ) ) : 0; // WPCS: input var ok, CSRF ok.
$max_price = isset( $_GET['max_price'] ) ? wc_clean( wp_unslash( $_GET['max_price'] ) ) : 0; // WPCS: input var ok, CSRF ok.
$rating_filter = isset( $_GET['rating_filter'] ) ? array_filter( array_map( 'absint', explode( ',', wp_unslash( $_GET['rating_filter'] ) ) ) ) : array(); // WPCS: sanitization ok, input var ok, CSRF ok.
$base_link = $this->get_current_page_url();
if ( 0 < count( $_chosen_attributes ) || 0 < $min_price || 0 < $max_price || ! empty( $rating_filter ) ) {
$this->widget_start( $args, $instance );
echo '<ul>';
// Attributes.
if ( ! empty( $_chosen_attributes ) ) {
foreach ( $_chosen_attributes as $taxonomy => $data ) {
foreach ( $data['terms'] as $term_slug ) {
$term = get_term_by( 'slug', $term_slug, $taxonomy );
if ( ! $term ) {
continue;
}
$filter_name = 'filter_' . wc_attribute_taxonomy_slug( $taxonomy );
$current_filter = isset( $_GET[ $filter_name ] ) ? explode( ',', wc_clean( wp_unslash( $_GET[ $filter_name ] ) ) ) : array(); // WPCS: input var ok, CSRF ok.
$current_filter = array_map( 'sanitize_title', $current_filter );
$new_filter = array_diff( $current_filter, array( $term_slug ) );
$link = remove_query_arg( array( 'add-to-cart', $filter_name ), $base_link );
if ( count( $new_filter ) > 0 ) {
$link = add_query_arg( $filter_name, implode( ',', $new_filter ), $link );
}
echo '<li class="chosen"><a rel="nofollow" aria-label="' . esc_attr__( 'Remove filter', 'woocommerce' ) . '" href="' . esc_url( $link ) . '">' . esc_html( $term->name ) . '</a></li>';
}
}
}
if ( $min_price ) {
$link = remove_query_arg( 'min_price', $base_link );
/* translators: %s: minimum price */
echo '<li class="chosen"><a rel="nofollow" aria-label="' . esc_attr__( 'Remove filter', 'woocommerce' ) . '" href="' . esc_url( $link ) . '">' . sprintf( __( 'Min %s', 'woocommerce' ), wc_price( $min_price ) ) . '</a></li>'; // WPCS: XSS ok.
}
if ( $max_price ) {
$link = remove_query_arg( 'max_price', $base_link );
/* translators: %s: maximum price */
echo '<li class="chosen"><a rel="nofollow" aria-label="' . esc_attr__( 'Remove filter', 'woocommerce' ) . '" href="' . esc_url( $link ) . '">' . sprintf( __( 'Max %s', 'woocommerce' ), wc_price( $max_price ) ) . '</a></li>'; // WPCS: XSS ok.
}
if ( ! empty( $rating_filter ) ) {
foreach ( $rating_filter as $rating ) {
$link_ratings = implode( ',', array_diff( $rating_filter, array( $rating ) ) );
$link = $link_ratings ? add_query_arg( 'rating_filter', $link_ratings ) : remove_query_arg( 'rating_filter', $base_link );
/* translators: %s: rating */
echo '<li class="chosen"><a rel="nofollow" aria-label="' . esc_attr__( 'Remove filter', 'woocommerce' ) . '" href="' . esc_url( $link ) . '">' . sprintf( esc_html__( 'Rated %s out of 5', 'woocommerce' ), esc_html( $rating ) ) . '</a></li>';
}
}
echo '</ul>';
$this->widget_end( $args );
}
}
}
Now, in your functions.php, insert the below code again. This will unregister the default widget and replace with the new one above where your custom design or layout exist.
function your_function_for_overriding_widgets() {
unregister_widget( 'WC_Widget_Layered_Nav_Filters' ); //unregistered the default widget
register_widget( 'Your_New_WC_Widget_Layered_Nav_Filters' ); //register your new widget
}
add_action( 'widgets_init', 'your_function_for_overriding_widgets' );

Woocommerce Product Quantity Filter Slider - Widget

I am trying to add a widget to Woocommerce to Filter Products by Quantity with a slider. I got this far by changing the open source code of woocommerce for the price filter slider. It print no errors once it's on the website but it does not filter the results. The output on the URL is also as expected. The slider does not work but it's fine if I cannot fix it. As of now it has two type-in fields: max and min stock quantity. Please help. I am sure others will benefit from this. I need it because the website is for wholesale and if a product is not available is a min quantity that a client wants, they can filter it out.
// Register and load the widget
function my_stock_widget() {
register_widget( 'WC_Widget_Stock_Filter' );
}
add_action( 'widgets_init', 'my_stock_widget' );
class WC_Widget_Stock_Filter extends WC_Widget {
/**
* Constructor.
*/
public function __construct() {
$this->widget_cssclass = 'woocommerce widget_stock_filter';
$this->widget_description = __( 'Shows a stock filter slider in a widget which lets you narrow down the list of shown products when viewing product categories.', 'woocommerce' );
$this->widget_id = 'woocommerce_stock_filter';
$this->widget_name = __( 'WooCommerce stock filter', 'woocommerce' );
$this->settings = array(
'title' => array(
'type' => 'text',
'std' => __( 'Filter by stock', 'woocommerce' ),
'label' => __( 'Title', 'woocommerce' ),
),
);
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
wp_register_script( 'accounting', WC()->plugin_url() . '/assets/js/accounting/accounting' . $suffix . '.js', array( 'jquery' ), '0.4.2' );
wp_register_script( 'wc-jquery-ui-touchpunch', WC()->plugin_url() . '/assets/js/jquery-ui-touch-punch/jquery-ui-touch-punch' . $suffix . '.js', array( 'jquery-ui-slider' ), WC_VERSION, true );
wp_register_script( 'wc-stock-slider', WC()->plugin_url() . '/assets/js/frontend/price-slider' . $suffix . '.js', array( 'jquery-ui-slider', 'wc-jquery-ui-touchpunch', 'accounting' ), WC_VERSION, true );
wp_localize_script( 'wc-stock-slider', 'woocommerce_stock_slider_params', array(
'min_stock' => isset( $_GET['min_stock'] ) ? esc_attr( $_GET['min_stock'] ) : '',
'max_stock' => isset( $_GET['max_stock'] ) ? esc_attr( $_GET['max_stock'] ) : '',
'currency_format_num_decimals' => 0,
// 'currency_format_symbol' => get_woocommerce_currency_symbol(),
'currency_format_decimal_sep' => esc_attr( wc_get_price_decimal_separator() ),
'currency_format_thousand_sep' => esc_attr( wc_get_price_thousand_separator() ),
// 'currency_format' => esc_attr( str_replace( array( '%1$s', '%2$s' ), array( '%s', '%v' ), get_woocommerce_stock_format() ) ),
) );
parent::__construct();
}
/**
* Output widget.
*
* #see WP_Widget
*
* #param array $args
* #param array $instance
*/
public function widget( $args, $instance ) {
global $wp, $wp_the_query;
if ( ! is_post_type_archive( 'product' ) && ! is_tax( get_object_taxonomies( 'product' ) ) ) {
return;
}
if ( ! $wp_the_query->post_count ) {
return;
}
$min_stock = isset( $_GET['min_stock'] ) ? esc_attr( $_GET['min_stock'] ) : '';
$max_stock = isset( $_GET['max_stock'] ) ? esc_attr( $_GET['max_stock'] ) : '';
wp_enqueue_script( 'wc-stock-slider' );
// Find min and max stock in current result set
$stocks = $this->get_filtered_stock();
$min = floor( $stocks->min_stock );
$max = ceil( $stocks->max_stock );
if ( $min === $max ) {
return;
}
$this->widget_start( $args, $instance );
if ( '' === get_option( 'permalink_structure' ) ) {
$form_action = remove_query_arg( array( 'page', 'paged' ), add_query_arg( $wp->query_string, '', home_url( $wp->request ) ) );
} else {
$form_action = preg_replace( '%\/page/[0-9]+%', '', home_url( trailingslashit( $wp->request ) ) );
}
/**
* Adjust max if the store taxes are not displayed how they are stored.
* Min is left alone because the product may not be taxable.
* Kicks in when stocks excluding tax are displayed including tax.
*
if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_stocks_include_tax() ) {
$tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() );
$class_max = $max;
foreach ( $tax_classes as $tax_class ) {
if ( $tax_rates = WC_Tax::get_rates( $tax_class ) ) {
$class_max = $max + WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $max, $tax_rates ) );
}
}
$max = $class_max;
}*/
echo '<form method="get" action="' . esc_url( $form_action ) . '">
<div class="stock_slider_wrapper">
<div class="stock_slider" style="display:none;"></div>
<div class="stock_slider_amount">
<input type="text" id="min_stock" name="min_stock" value="' . esc_attr( $min_stock ) . '" data-min="' . esc_attr( apply_filters( 'woocommerce_stock_filter_widget_min_amount', $min ) ) . '" placeholder="' . esc_attr__( 'Min stock', 'woocommerce' ) . '" />
<input type="text" id="max_stock" name="max_stock" value="' . esc_attr( $max_stock ) . '" data-max="' . esc_attr( apply_filters( 'woocommerce_stock_filter_widget_max_amount', $max ) ) . '" placeholder="' . esc_attr__( 'Max stock', 'woocommerce' ) . '" />
<button type="submit" class="button">' . esc_html__( 'Filter', 'woocommerce' ) . '</button>
<div class="stock_label" style="display:none;">
' . esc_html__( 'Stock:', 'woocommerce' ) . ' <span class="from"></span> — <span class="to"></span>
</div>
' . wc_query_string_form_fields( null, array( 'min_stock', 'max_stock' ), '', true ) . '
<div class="clear"></div>
</div>
</div>
</form>';
$this->widget_end( $args );
}
/**
* Get filtered min stock for current products.
* #return int
*/
protected function get_filtered_stock() {
global $wpdb, $wp_the_query;
$args = $wp_the_query->query_vars;
$tax_query = isset( $args['tax_query'] ) ? $args['tax_query'] : array();
$meta_query = isset( $args['meta_query'] ) ? $args['meta_query'] : array();
if ( ! is_post_type_archive( 'product' ) && ! empty( $args['taxonomy'] ) && ! empty( $args['term'] ) ) {
$tax_query[] = array(
'taxonomy' => $args['taxonomy'],
'terms' => array( $args['term'] ),
'field' => 'slug',
);
}
foreach ( $meta_query + $tax_query as $key => $query ) {
if ( ! empty( $query['stock_filter'] ) || ! empty( $query['rating_filter'] ) ) {
unset( $meta_query[ $key ] );
}
}
$meta_query = new WP_Meta_Query( $meta_query );
$tax_query = new WP_Tax_Query( $tax_query );
$meta_query_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );
$tax_query_sql = $tax_query->get_sql( $wpdb->posts, 'ID' );
$sql = "SELECT min( FLOOR( stock_meta.meta_value ) ) as min_stock, max( CEILING( stock_meta.meta_value ) ) as max_stock FROM {$wpdb->posts} ";
$sql .= " LEFT JOIN {$wpdb->postmeta} as stock_meta ON {$wpdb->posts}.ID = stock_meta.post_id " . $tax_query_sql['join'] . $meta_query_sql['join'];
$sql .= " WHERE {$wpdb->posts}.post_type IN ('" . implode( "','", array_map( 'esc_sql', apply_filters( 'woocommerce_stock_filter_meta_keys', array( 'product' ) ) ) ) . "')
AND {$wpdb->posts}.post_status = 'publish'
AND stock_meta.meta_key IN ('" . implode( "','", array_map( 'esc_sql', apply_filters( 'woocommerce_stock_filter_meta_keys', array( '_stock' ) ) ) ) . "')
AND stock_meta.meta_value > '' ";
$sql .= $tax_query_sql['where'] . $meta_query_sql['where'];
if ( $search = WC_Query::get_main_search_query_sql() ) {
$sql .= ' AND ' . $search;
}
return $wpdb->get_row( $sql );
}

Woocommerce Variable products dropdown

I am trying to implement quantity dropdown for my woocommerce shop, i found the code below to enable for single products but I cannot get it working for products with variations.
<?php
// Place the following code in your theme's functions.php file
// override the quantity input with a dropdown
// Note that you still have to invoke this function like this:
/*
$product_quantity = woocommerce_quantity_input( array(
'input_name' => "cart[{$cart_item_key}][qty]",
'input_value' => $cart_item['quantity'],
'max_value' => $_product->backorders_allowed() ? '' : $_product->get_stock_quantity(),
'min_value' => '0'
), $_product, false );
*/
function woocommerce_quantity_input($data) {
global $product;
$defaults = array(
'input_name' => $data['input_name'],
'input_value' => $data['input_value'],
'max_value' => apply_filters( 'woocommerce_quantity_input_max', '', $product ),
'min_value' => apply_filters( 'woocommerce_quantity_input_min', '', $product ),
'step' => apply_filters( 'woocommerce_quantity_input_step', '1', $product ),
'style' => apply_filters( 'woocommerce_quantity_style', 'float:left; margin-right:10px;', $product )
);
if ( ! empty( $defaults['min_value'] ) )
$min = $defaults['min_value'];
else $min = 1;
if ( ! empty( $defaults['max_value'] ) )
$max = $defaults['max_value'];
else $max = 20;
if ( ! empty( $defaults['step'] ) )
$step = $defaults['step'];
else $step = 1;
$options = '';
for ( $count = $min; $count <= $max; $count = $count+$step ) {
$selected = $count === $defaults['input_value'] ? ' selected' : '';
$options .= '<option value="' . $count . '"'.$selected.'>' . $count . '</option>';
}
echo '<div class="quantity_select" style="' . $defaults['style'] . '"><select name="' . esc_attr( $defaults['input_name'] ) . '" title="' . _x( 'Qty', 'Product quantity input tooltip', 'woocommerce' ) . '" class="qty">' . $options . '</select></div>';
}
?>
The result I am after is something like this image:
How can this be done with Woocommerce?
Here's an updated version of woocommerce_quantity_input()
/**
* Output the quantity input for add to cart forms.
*
* #param array $args Args for the input
* #param WC_Product|null $product
* #param boolean $echo Whether to return or echo|string
*/
function woocommerce_quantity_input( $args = array(), $product = null, $echo = true ) {
if ( is_null( $product ) ) {
$product = $GLOBALS['product'];
}
$defaults = array(
'input_name' => 'quantity',
'input_value' => '1',
'max_value' => apply_filters( 'woocommerce_quantity_input_max', '20', $product ),
'min_value' => apply_filters( 'woocommerce_quantity_input_min', '0', $product ),
'step' => apply_filters( 'woocommerce_quantity_input_step', '1', $product ),
'pattern' => apply_filters( 'woocommerce_quantity_input_pattern', has_filter( 'woocommerce_stock_amount', 'intval' ) ? '[0-9]*' : '' ),
'inputmode' => apply_filters( 'woocommerce_quantity_input_inputmode', has_filter( 'woocommerce_stock_amount', 'intval' ) ? 'numeric' : '' ),
);
$args = apply_filters( 'woocommerce_quantity_input_args', wp_parse_args( $args, $defaults ), $product );
// Set min and max value to empty string if not set.
$args['min_value'] = isset( $args['min_value'] ) ? $args['min_value'] : '1';
$args['max_value'] = isset( $args['max_value'] ) ? $args['max_value'] : '20';
// Apply sanity to min/max args - min cannot be lower than 0
if ( '' !== $args['min_value'] && is_numeric( $args['min_value'] ) && $args['min_value'] < 0 ) {
$args['min_value'] = 0; // Cannot be lower than 0
}
// Max cannot be lower than 0 or min
if ( '' !== $args['max_value'] && is_numeric( $args['max_value'] ) ) {
$args['max_value'] = $args['max_value'] < 0 ? 0 : $args['max_value'];
$args['max_value'] = $args['max_value'] < $args['min_value'] ? $args['min_value'] : $args['max_value'];
}
ob_start();
$options = '';
for ( $count = $args['min_value']; $count <= $args['max_value']; $count = $count + $args['step'] ) {
$options .= '<option value="' . $count . '"'. selected( $count, $args['input_value'], false ) .'>' . $count . '</option>';
}
echo '<div class="quantity_select" style="' . $args['style'] . '"><select name="' . esc_attr( $args['input_name'] ) . '" title="' . _x( 'Qty', 'Product quantity input tooltip', 'woocommerce' ) . '" class="qty">' . $options . '</select></div>';
if ( $echo ) {
echo ob_get_clean();
} else {
return ob_get_clean();
}
}
Turns out the best way of doing this was to use grouped products

Categories