The shop has custom post type product and category for products product_cat. By default product links looks like /product/product-name/.

How to change it to /{product category name}/{product name}/?

For example we want to change /product/burton-cartel/ to /snowboard-bindings/burton-cartel/.

The following code will let you do that. The code is for your current theme functions.php file and works great with the following permalink settings:

Rewrite Post type links and Change default request

add_filter('post_link', 'rudr_post_type_permalink', 20, 3);
add_filter('post_type_link', 'rudr_post_type_permalink', 20, 3);

function rudr_post_type_permalink($permalink, $post_id, $leavename) {

	$post_type_name = 'product'; // post type name, you can find it in admin area or in register_post_type() function
	$post_type_slug = 'product'; // the part of your product URLs, not always matches with the post type name
	$tax_name = 'product_cat'; // the product categories taxonomy name

	$post = get_post( $post_id );

	if ( strpos( $permalink, $post_type_slug ) === FALSE || $post->post_type != $post_type_name ) // do not make changes if the post has different type or its URL doesn't contain the given post type slug
		return $permalink;

        $terms = wp_get_object_terms( $post->ID, $tax_name ); // get all terms (product categories) of this post (product)


        if ( !is_wp_error( $terms ) && !empty( $terms ) && is_object( $terms[0] ) ) // rewrite only if this product has categories
        	$permalink = str_replace( $post_type_slug, $terms[0]->slug, $permalink );

	return $permalink;
}


add_filter('request', 'rudr_post_type_request', 1, 1 );

function rudr_post_type_request( $query ){
	global $wpdb;
	
	$post_type_name = 'product'; // specify your own here
	$tax_name = 'product_cat'; // and here
	
	$slug = $query['attachment']; // when we change the post type link, WordPress thinks that these are attachment pages

	// get the post with the given type and slug from the database
	$post_id = $wpdb->get_var(
		"
		SELECT ID
		FROM $wpdb->posts
		WHERE post_name = '$slug'
		AND post_type = '$post_type_name'
		"
	);
	
	$terms = wp_get_object_terms( $post_id, $tax_name ); // our post should have the terms
	
	
	if( isset( $slug ) && $post_id && !is_wp_error( $terms ) && !empty( $terms ) ) : // change the query
	
		unset( $query['attachment'] );
		$query[$post_type_name] = $slug;
		$query['post_type'] = $post_type_name;
		$query['name'] = $slug;
		
	endif;
	
	return $query;
}

How to perform 301 redirect from the old post URLs

add_action('template_redirect', 'rudr_post_type_redirect');

function rudr_post_type_redirect() {
	
	$post_type_name = 'product'; // specify your own here
	$post_type_slug = 'product'; // here
	$tax_name = 'platform'; // and here
	
	if( strpos( $_SERVER['REQUEST_URI'], $post_type_slug ) === FALSE) // do not redirect if the URL doesn't contain the given post type slug
		return;

	if( is_singular( $post_type_name ) ) : // if post type page
		global $post, $wp_rewrite;
		
		$terms = wp_get_object_terms( $post->ID, $tax_name ); // get terms attached
		
		if ( !is_wp_error( $terms ) && !empty( $terms ) && is_object( $terms[0] ) ) :
			
			wp_redirect( site_url() . '/' . $wp_rewrite->front . '/' . $terms[0]->slug . '/' . $post->post_name, 301 );
			// wp_redirect( get_permalink( $post->ID ), 301 ); // depends on the previous code from this post

			exit();
        	endif;
	endif;

}