Question

I would like to have a separate url for each of my product variations. For that I would need to create a rewrite rule, that will work on the product URLs

Ie, my product URL is https://example.com/product/tshirt/

The product has two variations - size and color. I would like to be able to access the product on url like https://example.com/product/tshirt/size-s-color-black

This clearly has something to do with the add_rewrite_rule function. I don't know how to build the url structure to be able to access the variations part of url

function custom_rewrite_basic() {
  add_rewrite_rule('^product/([0-9]+)/([0-9]+)?', 'index.php?page_id=$matches[1]&variations=$matches[2]', 'top');
}
add_action('init', 'custom_rewrite_basic');

Anyone?

Was it helpful?

Solution

Ok, for anyone looking for this, here's a complete plugin that I came up with, that solves the issue:

<?php
/*
Plugin Name: WooCommerce Variations URL
Description: Adds support for variation-specific URL's for WooCommerce product variations
Author: Václav Greif
Version: 1.0
Author URI: https://wp-programator.cz
*/

namespace WCVariationsUrl;

final class Init
{
    /**
     * Call this method to get singleton
     *
     * @return Init
     */
    public static function Instance()
    {
        static $inst = null;
        if ($inst === null) {
            $inst = new Init();
        }
        return $inst;
    }

    /**
     * Private ctor so nobody else can instance it
     *
     */
    private function __construct()
    {
        $this->add_actions();
    }

    /**
     * Add actions
     */
    function add_actions() {
        add_filter('woocommerce_dropdown_variation_attribute_options_args',array($this,'variation_dropdown_args'));
        add_action('init', array($this,'add_rewrite_rules'));
        add_action('wp_head', array($this,'add_js_to_head'));

    }

    function variation_dropdown_args($args) {
        // Get the WooCommerce atts
        $attributes =  wc_get_attribute_taxonomies();
        $atts = [];
        foreach ($attributes as $attribute) {
            $atts[] = $attribute->attribute_name;
        }

        // Get the variations part of URL
        $url_string = get_query_var('variation');

        if ($url_string) {
            $array = [];
            preg_replace_callback(
                "/(\w++)(?>-(\w+-?(?(?!" . implode("|", $atts) . ")(?-1))*))/",
                function($matches) use (&$array) {
                    $array[$matches[1]] = rtrim($matches[2], '-');
                },
                $url_string
            );

            if (!empty($array)) {
                $attribute_key = str_replace('pa_','',$args['attribute']);

                if (array_key_exists($attribute_key,$array)) {
                    $args['selected'] = $array[$attribute_key];
                }
            }
        }

        return $args;

    }

    function add_rewrite_rules() {
        add_rewrite_rule('^product/([^/]*)/([^/]*)/?','index.php?product=$matches[1]&variation=$matches[2]','top');
        add_rewrite_tag('%variation%', '([^&]+)');
    }

    function add_js_to_head() {
        if (!function_exists('is_product') || !is_product())
            return;

        global $post;
        ?>

        <script type="text/javascript">
            var url = '<?php echo get_permalink($post->ID);?>';
            jQuery( document ).ready(function($) {
                setTimeout(
                    function() {
                        $('table.variations select').on('change',function() {
                            var attributes = [];
                            var allAttributesSet = true;
                            $('table.variations select').each(function() {
                                var value = $(this).val();
                                if (value) {
                                    attributes.push({
                                        id: $(this).attr('name'),
                                        value: value
                                    });
                                } else {
                                    allAttributesSet = false;
                                }
                            });

                            if (allAttributesSet) {
                                $.each(attributes,function(key, val) {
                                    var attributeSlug = val.id.replace('attribute_pa_','');
                                    url = url + attributeSlug + '-' + val.value
                                    if($(this)[0] !== $(attributes).last()[0]) {
                                        url = url + '-';
                                    }
                                });
                                window.location.replace(url);
                            }
                        });

                    }, 1000
                )
            });
        </script>
    <?php }
}

Init::Instance();

OTHER TIPS

My intention: On change reload page, pass the ID as URL?attribute so i can get it on next page as GET. I had simmilar intention. But I wanted to pass this into the URL as an better attribute. The advice here did not really suited my intention so I a bit modified it for my purpose. The shop I needed this was really using intentionally and specifically only single variations, no 2-3 mixed together.

<script>
        jQuery(function($){
            setTimeout( function(){
                $( ".single_variation_wrap" ).on( "show_variation", function ( event, variation ) {
                    //alert( variation.variation_id );
                    console.log( variation.variation_id );


                    var url = '<?php echo get_permalink($post->ID);?>';
                    //$('input.variation_id').change( function(){


                    console.log('You just selected variation #' + variation.variation_id);
                    var attributes = [];
                    var allAttributesSet = true;
                    $('table.variations select').each(function() {
                        var value = $(this).val();
                        if (value) {
                            attributes.push({
                                id: $(this).attr('name'),
                                value: value
                            });
                        } else {
                            allAttributesSet = false;
                        }
                    });
                    if (allAttributesSet) {
                        $.each(attributes,function(key, val) {
                            var attributeSlug = val.id.replace('attribute_pa_','');
                            url = url +'?variation_id='+ variation.variation_id +'&'+attributeSlug+'=' + val.value;
                        });
                        console.log('Relocating #' + variation.variation_id);
                        //window.location.replace(url);
                        window.location.href = url;
                    }

                } );
            }, 400 );

        });
    </script>

Not sure if this is the best and good approach, but this is what is working for me, and it even keeps the selected values in Variation dropdown since it is only single one.

I am calling the script above in this action:

add_action( 'woocommerce_before_add_to_cart_quantity', 'destone_variation_modification' );

Licensed under: CC-BY-SA with attribution
Not affiliated with wordpress.stackexchange
scroll top