Product variations and weight rate shipping- MP 2.5

This one is for Aaron, I was too slow to respond during the Beta phase so I'm starting a new thread for it.

The weight and calculated shipping stuff looks good, but I noticed that there is no way to accommodate weight levels for product variations.

In my use case, pretty much everything that is sold on our network is by weight. Variations are added in reference to greater weight (Variation 1 = 250 grams; Variation 2 = 500 grams; Variation 3 = 1 kilo; etc).

Attaching weight to variations will make the calculated weight shipping modules work better, otherwise, when it comes to sites where quantity variations can be based on weight, the modules are unusable.

  • Aaron

    Variations are more meant for things like colors/sizes/etc. If you notice, most other product details are variation agnostic as well, such as the download, product link, extra shipping, special tax rate, etc.

    I understand your desire for this, but it will be a long while probably before this is implemented as its a major core code shift. For now you would need to create separate products for unique weights.

  • dookiedigital

    I am also looking for this as it is key to my clients shipping calculations too.

    Variations typically include size, and depending on the type of product size can often mean a significant weight difference.

    The solution just now is simply to set the weight to be the greatest of the variations, but then that means deliberately overcharging shipping in many cases.

    I could have a separate product, but it would be confusing for the customer to have two almost identical products.

    My use case is framed prints at A3 and A4 sizes.

  • kimedia

    I actually solved this for myself yesterday within a couple of hours. It wasn't too bad.

    Most of it was done in my functions file - so as not to overwrite any core files. And also I had to create a separate shipping file in /marketpress/marketpress-includes/plugins-shipping/

    That was mostly a case of running a find and replace to create unique fields within the shipping array and then writing in the logic to calculate.

    In my case the problem ended up being about variants requiring to be either shipped or not. The client had hard back versions of books and e-published versions of books to be emailed (I am aware about the download option, client didn't want to do that and this can't be done on a variant basis anyway)

    So,
    First I had to take control of the marketpress meta_boxes to add my new field for 'requires shipping', or 'no shipping required'

    In the functions file I put:

    remove_action( 'add_meta_boxes_product', array(&$mp, 'meta_boxes') );
    add_action( 'add_meta_boxes_product', 'ki_meta_boxes' );

    My new function of ki_meta_boxes is essentially a copy and paste of 'meta_boxes' adding in a global $mp so i can use all of market_press' class functions (I'm not sure if this is normal or not, or if I created the $mp class variable myself ages ago - but it is generally a good idea so you can have more control over things without hacking core files)

    function ki_meta_boxes() {
      global $wp_meta_boxes,$mp;
      $settings = get_option('mp_settings');
    
      add_meta_box('mp-meta-details', __('Product Details', 'mp'), 'ki_meta_details', 'product', 'normal', 'high');
    
      //only add these boxes if orders are enabled
      if (!$settings['disable_cart']) {
    
        //only display metabox if shipping plugin ties into it
        if ( has_action('mp_shipping_metabox') )
          add_meta_box('mp-meta-shipping', __('Shipping', 'mp'), array(&$mp, 'meta_shipping'), 'product', 'normal', 'high');
    
        //for product downloads
        add_meta_box('mp-meta-download', __('Product Download', 'mp'), array(&$mp, 'meta_download'), 'product', 'normal', 'high');
      }
    
      //all this junk is to reorder the metabox array to move the featured image box to the top right below submit box. User order will override
      if ( isset( $wp_meta_boxes['product']['side']['low']['postimagediv'] ) ) {
        $imagediv = $wp_meta_boxes['product']['side']['low']['postimagediv'];
        unset( $wp_meta_boxes['product']['side']['low'] );
        $submitdiv = $wp_meta_boxes['product']['side']['core']['submitdiv'];
        unset( $wp_meta_boxes['product']['side']['core']['submitdiv'] );
        $new_core['submitdiv'] = $submitdiv;
        $new_core['postimagediv'] = $imagediv;
        $wp_meta_boxes['product']['side']['core'] = array_merge( $new_core, $wp_meta_boxes['product']['side']['core']) ;
        //filter title
        $wp_meta_boxes['product']['side']['core']['postimagediv']['title'] = __('Product Image', 'mp');
      }
    }

    Taking control of this function gives me the opportunity to take control over the meta_details function - which is where I can add in an extra field for whatever I want to to with variables.

    add_meta_box('mp-meta-details', __('Product Details', 'mp'), 'ki_meta_details', 'product', 'normal', 'high');

    the 'ki_meta_details' function is again a copy and paste of the orginal marketpress meta_details function, with my new fields added:

    function ki_meta_details() {
      global $post,$mp;
      $settings = get_option('mp_settings');
      $meta = get_post_custom($post->ID);
      //unserialize
      foreach ($meta as $key => $val) {
        $meta[$key] = maybe_unserialize($val[0]);
        if (!is_array($meta[$key]) && $key != "mp_is_sale" && $key != "mp_track_inventory" && $key != "mp_product_link" && $key != "mp_file" && $key != "mp_is_special_tax" && $key != "mp_special_tax")
          $meta[$key] = array($meta[$key]);
      }
      ?>
        <input type="hidden" name="mp_product_meta" value="1" />
        <table class="widefat" id="mp_product_variations_table">
    			<thead>
    				<tr>
    					<th scope="col" class="mp_var_col"><?php _e('Variation Name', 'mp') ?></th>
    					<th scope="col" class="mp_sku_col" title="<?php _e('Stock Keeping Unit - Your custom Product ID number', 'mp'); ?>"><?php _e('SKU', 'mp') ?></th>
    					<th scope="col" class="mp_price_col"><?php _e('Price', 'mp') ?></th>
    					<th scope="col" class="mp_sale_col"><label title="<?php _e('When checked these override the normal price.', 'mp'); ?>"><input type="checkbox" id="mp_is_sale" name="mp_is_sale" value="1"<?php checked($meta["mp_is_sale"], '1'); ?> /> <?php _e('Sale Price', 'mp') ?></label></th>
    					<th scope="col" class="mp_inv_col"><label title="<?php _e('When checked inventory tracking will be enabled.', 'mp'); ?>"><input type="checkbox" id="mp_track_inventory" name="mp_track_inventory" value="1"<?php checked($meta["mp_track_inventory"], '1'); ?> /> <?php _e('Inventory', 'mp') ?></label></th>
    					<th scope="col" class="mp_toship_col"><?php _e('Variation requires shipping.', 'mp'); ?></th>
    					<th scope="col" class="mp_var_remove"></th>
    				</tr>
    			</thead>
    			<tbody>
    			<?php
    			  if ($meta["mp_price"]) {
    			    //if download enabled only show first variation
    			    $meta["mp_price"] = (empty($meta["mp_file"]) && empty($meta["mp_product_link"])) ? $meta["mp_price"] : array($meta["mp_price"][0]);
    			    $count = 1;
    			    $last = count($meta["mp_price"]);
    	        foreach ($meta["mp_price"] as $key => $price) {
    		        ?>
    						<tr class="variation">
    							<td class="mp_var_col"><input type="text" name="mp_var_name[]" value="<?php echo esc_attr($meta["mp_var_name"][$key]); ?>" /></td>
    							<td class="mp_sku_col"><input type="text" name="mp_sku[]" value="<?php echo esc_attr($meta["mp_sku"][$key]); ?>" /></td>
    							<td class="mp_price_col"><?php echo $mp->format_currency(); ?><input type="text" name="mp_price[]" value="<?php echo isset($meta["mp_price"][$key]) ? $mp->display_currency($meta["mp_price"][$key]) : '0.00'; ?>" /></td>
    							<td class="mp_sale_col"><?php echo $mp->format_currency(); ?><input type="text" name="mp_sale_price[]" value="<?php echo isset($meta["mp_sale_price"][$key]) ? $mp->display_currency($meta["mp_sale_price"][$key]) : $mp->display_currency($meta["mp_price"][$key]); ?>" disabled="disabled" /></td>
                  <td class="mp_inv_col"><input type="text" name="mp_inventory[]" value="<?php echo intval($meta["mp_inventory"][$key]); ?>" disabled="disabled" /></td>
                  <td class="mp_topship_col">
                  <select name="mp_toship[]">
                <option value="1"<?php if(isset($meta["mp_toship"][$key]) && $meta["mp_toship"][$key] == 1) { echo 'selected="selected"'; }?>>Yes</option>
                <option value="0"<?php if(isset($meta["mp_toship"][$key]) && $meta["mp_toship"][$key] == 0) { echo 'selected="selected"'; }?>>No</option>
                </select></td>
    							<td class="mp_var_remove">
    							<?php if ($count == $last) { ?><a href="#mp_product_variations_table" title="<?php _e('Remove Variation', 'mp'); ?>">x</a><?php } ?>
    							</td>
    						</tr>
    						<?php
    						$count++;
    					}
    	      } else {
           		?>
    					<tr class="variation">
    						<td class="mp_var_col"><input type="text" name="mp_var_name[]" value="" /></td>
    						<td class="mp_sku_col"><input type="text" name="mp_sku[]" value="" /></td>
    						<td class="mp_price_col"><?php echo $mp->format_currency(); ?><input type="text" name="mp_price[]" value="0.00" /></td>
    						<td class="mp_sale_col"><?php echo $mp->format_currency(); ?><input type="text" name="mp_sale_price[]" value="0.00" disabled="disabled" /></td>
                <td class="mp_inv_col"><input type="text" name="mp_inventory[]" value="0" disabled="disabled" /></td>
                <td class="mp_topship_col">
                <select name="mp_toship[]">
                <option value="1">Yes</option>
                <option value="0">No</option>
                </select></td>
    						<td class="mp_var_remove"><a href="#mp_product_variations_table" title="<?php _e('Remove Variation', 'mp'); ?>">x</a></td>
    					</tr>
    					<?php
    	      }
    			?>
    			</tbody>
    		</table>
    		<?php if (empty($meta["mp_file"]) && empty($meta["mp_product_link"])) { ?>
    		<div id="mp_add_vars"><a href="#mp_product_variations_table"><?php _e('Add Variation', 'mp'); ?></a></div>
    		<?php } else { ?>
        <span class="description" id="mp_variation_message"><?php _e('Product variations are not allowed for Downloadable or Externally Linked products.', 'mp') ?></span>
        <?php } ?>
    
        <div id="mp_product_link_div">
          <label title="<?php _e('Some examples are linking to a song/album in iTunes, or linking to a product on another site with your own affiliate link.', 'mp'); ?>"><?php _e('External Link', 'mp'); ?>:<br /><small><?php _e('When set this overrides the purchase button with a link to this URL.', 'mp'); ?></small><br />
          <input type="text" style="width: 100%;" id="mp_product_link" name="mp_product_link" value="<?php echo esc_url($meta["mp_product_link"]); ?>" /></label>
        </div>
    
    		<div id="mp_tax_rate_div">
    		<label title="<?php esc_attr_e('Depending on local tax laws, some items are tax-free or a have different sales tax rate. You can set that here.', 'mp'); ?>"><input type="checkbox" id="mp_is_special_tax" name="mp_is_special_tax" value="1" <?php checked($meta["mp_is_special_tax"]); ?>/> <?php _e('Special Tax Rate?', 'mp'); ?></label>
    		<label id="mp_special_tax"<?php echo ($meta["mp_is_special_tax"]) ? '' : ' style="display:none;"'; ?>><?php _e('Rate:', 'mp'); ?> <input type="text" size="2" name="mp_special_tax" value="<?php echo isset($meta["mp_special_tax"]) ? round($meta["mp_special_tax"] * 100, 3) : 0; ?>" />%</label>
    		</div>
    
        <?php do_action( 'mp_details_metabox' ); ?>
        <div class="clear"></div>
        <?php
      }

    Again I throw in a global $mp and find/replace all instances of $this with $mp to tie into the orginal functions where necessary.

    I add in a new header of 'requires shipping'

    and a new field called mp_toship, which is an array that collects if the variant needs shipping or not.
    This could just as easily be an input field which collects the variant's weight in order to calculate shipping - or whichever other modification you wish

    After that we need to store the new data in the database.

    remove_action( 'wp_insert_post', array(&$mp, 'save_product_meta'), 10, 2 );
    add_action( 'wp_insert_post', 'ki_save_product_meta', 10, 2 );

    I remove the marketpress insert posts hook and add my own.

    Again, copy and paste the original, throw a global $mp, find and replace all instances of $this for $mp and then you can add in your own code to store your new variable.

    Which in my case was adding the line:

    update_post_meta($post_id, 'mp_toship', $_POST['mp_toship']);

    after all the other update_post_meta statements where it stores all of the other variables.

    Everything else stays the same so you don't break anything.

    From there you have to create your own shipping calculation plugin.

    I advise that you take the plugin closest to what you need.
    Copy it
    rename the class, the public name, the plugin name

    then find and replace all of the input fields.

    For example if editing the flat-rate plugin you would need to find/replace

    mp[shipping][flat-rate] and $settings['shipping']['flat-rate']

    with something unique - I used

    mp[shipping][ki-flat-rate] and $settings['shipping']['ki-flat-rate']

    The next bit is a bit more bespoke as it depends on what you are trying to achieve.

    But in the calculate_shipping function of your new shipping plugin you need to start working out the logic of what you are trying to do.

    In my case, I simply needed to run through the basket, make sure that there is at least one product that requires shipping, and if so calculate shipping. If not then set it to 0.

    The important bit is how to get your new variation variable out of the cart loop.

    When looping through the cart, within each variation you can call get_post_meta on the product_id and call out the variable you use in the database.

    It will return an array but you can check the value of that particular variation by returning it's position in the array $toship[$variation]

    e.g.

    foreach ($cart as $product_id => $variations) {
    
          foreach ($variations as $variation => $data) {
    
            $toship = maybe_unserialize(get_post_meta($product_id, 'mp_toship', true));
    
            if ($toship[$variation] == 1) {
             $ship = true;
            }
    
          }
        }

    I'm aware this is a little inelegant and hacky - but needs must and it doesn't actually take very long.

    Hope it helps some of you out. If i need to be clearer on any points give me a shout

  • qtree-systems

    I face the exact same issue of product variations changing the shipping calculations for a furniture project i am working on. King size cost more to ship than a twin, double, etc.

    Creating a different product for every size will complicate the situation for customers.

    This thread is over a year old, so I was wondering if there is a solution for this? or is @kimedia 's solution the only viable option that works?

    thanks,

    Steve

  • qtree-systems

    @Jack Kitterhing

    use table rate shipping and then if your variation costs more, the higher shipping cost would be applicable.

    How would you tie this to the Variations? ((LOL ok table rate tied to price sorry it is Saturday))

    But on this project I need to use FedEx so would you recommend @kimedia's solution? and that post is 1 year old, will it work with current version?

    Thanks!!

  • Jack Kitterhing

    Hi there @qtree-systems,

    Hope you're well today. :slight_smile:

    It should work, though as you said it is over a year old and so there may be a few issues, could you open a new thread here https://premium.wpmudev.org/forums/#question

    Link back to this thread in the content and then I can get one of our developers to take a second look at this for you. :slight_smile:

    Thanks for your understanding!

    Kind Regards
    Jack.

Thank NAME, for their help.

Let NAME know exactly why they deserved these points.

Gift a custom amount of points.