1 <?php
   2 /**
   3  * Product Data
   4  *
   5  * Displays the product data box, tabbed, with several panels covering price, stock etc.
   6  *
   7  * @author      WooThemes
   8  * @category    Admin
   9  * @package     WooCommerce/Admin/Meta Boxes
  10  * @version     2.1.0
  11  */
  12 
  13 if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
  14 
  15 /**
  16  * WC_Meta_Box_Product_Data
  17  */
  18 class WC_Meta_Box_Product_Data {
  19 
  20     /**
  21      * Output the metabox
  22      */
  23     public static function output( $post ) {
  24         global $post, $wpdb, $thepostid;
  25 
  26         wp_nonce_field( 'woocommerce_save_data', 'woocommerce_meta_nonce' );
  27 
  28         $thepostid = $post->ID;
  29 
  30         if ( $terms = wp_get_object_terms( $post->ID, 'product_type' ) )
  31             $product_type = sanitize_title( current( $terms )->name );
  32         else
  33             $product_type = apply_filters( 'default_product_type', 'simple' );
  34 
  35         $product_type_selector = apply_filters( 'product_type_selector', array(
  36             'simple'    => __( 'Simple product', 'woocommerce' ),
  37             'grouped'   => __( 'Grouped product', 'woocommerce' ),
  38             'external'  => __( 'External/Affiliate product', 'woocommerce' ),
  39             'variable'  => __( 'Variable product', 'woocommerce' )
  40         ), $product_type );
  41 
  42         $type_box  = '<label for="product-type"><select id="product-type" name="product-type"><optgroup label="' . __( 'Product Type', 'woocommerce' ) . '">';
  43         foreach ( $product_type_selector as $value => $label )
  44             $type_box .= '<option value="' . esc_attr( $value ) . '" ' . selected( $product_type, $value, false ) .'>' . esc_html( $label ) . '</option>';
  45         $type_box .= '</optgroup></select></label>';
  46 
  47         $product_type_options = apply_filters( 'product_type_options', array(
  48             'virtual' => array(
  49                 'id'            => '_virtual',
  50                 'wrapper_class' => 'show_if_simple',
  51                 'label'         => __( 'Virtual', 'woocommerce' ),
  52                 'description'   => __( 'Virtual products are intangible and aren\'t shipped.', 'woocommerce' ),
  53                 'default'       => 'no'
  54             ),
  55             'downloadable' => array(
  56                 'id'            => '_downloadable',
  57                 'wrapper_class' => 'show_if_simple',
  58                 'label'         => __( 'Downloadable', 'woocommerce' ),
  59                 'description'   => __( 'Downloadable products give access to a file upon purchase.', 'woocommerce' ),
  60                 'default'       => 'no'
  61             )
  62         ) );
  63 
  64         foreach ( $product_type_options as $key => $option ) {
  65             $selected_value = get_post_meta( $post->ID, '_' . $key, true );
  66 
  67             if ( $selected_value == '' && isset( $option['default'] ) ) {
  68                 $selected_value = $option['default'];
  69             }
  70 
  71             $type_box .= '<label for="' . esc_attr( $option['id'] ) . '" class="'. esc_attr( $option['wrapper_class'] ) . ' tips" data-tip="' . esc_attr( $option['description'] ) . '">' . esc_html( $option['label'] ) . ': <input type="checkbox" name="' . esc_attr( $option['id'] ) . '" id="' . esc_attr( $option['id'] ) . '" ' . checked( $selected_value, 'yes', false ) .' /></label>';
  72         }
  73 
  74         ?>
  75         <div class="panel-wrap product_data">
  76 
  77             <span class="type_box"> &mdash; <?php echo $type_box; ?></span>
  78 
  79             <div class="wc-tabs-back"></div>
  80 
  81             <ul class="product_data_tabs wc-tabs" style="display:none;">
  82                 <?php
  83                     $product_data_tabs = apply_filters( 'woocommerce_product_data_tabs', array(
  84                         'general' => array(
  85                             'label'  => __( 'General', 'woocommerce' ),
  86                             'target' => 'general_product_data',
  87                             'class'  => array( 'hide_if_grouped' ),
  88                         ),
  89                         'inventory' => array(
  90                             'label'  => __( 'Inventory', 'woocommerce' ),
  91                             'target' => 'inventory_product_data',
  92                             'class'  => array( 'show_if_simple', 'show_if_variable', 'show_if_grouped' ),
  93                         ),
  94                         'shipping' => array(
  95                             'label'  => __( 'Shipping', 'woocommerce' ),
  96                             'target' => 'shipping_product_data',
  97                             'class'  => array( 'hide_if_virtual', 'hide_if_grouped', 'hide_if_external' ),
  98                         ),
  99                         'linked_product' => array(
 100                             'label'  => __( 'Linked Products', 'woocommerce' ),
 101                             'target' => 'linked_product_data',
 102                             'class'  => array(),
 103                         ),
 104                         'attribute' => array(
 105                             'label'  => __( 'Attributes', 'woocommerce' ),
 106                             'target' => 'product_attributes',
 107                             'class'  => array(),
 108                         ),
 109                         'variations' => array(
 110                             'label'  => __( 'Variations', 'woocommerce' ),
 111                             'target' => 'variable_product_options',
 112                             'class'  => array( 'variations_tab', 'show_if_variable' ),
 113                         ),
 114                         'advanced' => array(
 115                             'label'  => __( 'Advanced', 'woocommerce' ),
 116                             'target' => 'advanced_product_data',
 117                             'class'  => array(),
 118                         )
 119                     ) );
 120 
 121                     foreach ( $product_data_tabs as $key => $tab ) {
 122                         ?><li class="<?php echo $key; ?>_options <?php echo $key; ?>_tab <?php echo implode( ' ' , $tab['class'] ); ?>">
 123                             <a href="#<?php echo $tab['target']; ?>"><?php echo esc_html( $tab['label'] ); ?></a>
 124                         </li><?php
 125                     }
 126 
 127                     do_action( 'woocommerce_product_write_panel_tabs' );
 128                 ?>
 129             </ul>
 130             <div id="general_product_data" class="panel woocommerce_options_panel"><?php
 131 
 132                 echo '<div class="options_group hide_if_grouped">';
 133 
 134                     // SKU
 135                     if( wc_product_sku_enabled() )
 136                         woocommerce_wp_text_input( array( 'id' => '_sku', 'label' => '<abbr title="'. __( 'Stock Keeping Unit', 'woocommerce' ) .'">' . __( 'SKU', 'woocommerce' ) . '</abbr>', 'desc_tip' => 'true', 'description' => __( 'SKU refers to a Stock-keeping unit, a unique identifier for each distinct product and service that can be purchased.', 'woocommerce' ) ) );
 137                     else
 138                         echo '<input type="hidden" name="_sku" value="' . esc_attr( get_post_meta( $thepostid, '_sku', true ) ) . '" />';
 139 
 140                     do_action('woocommerce_product_options_sku');
 141 
 142                 echo '</div>';
 143 
 144                 echo '<div class="options_group show_if_external">';
 145 
 146                     // External URL
 147                     woocommerce_wp_text_input( array( 'id' => '_product_url', 'label' => __( 'Product URL', 'woocommerce' ), 'placeholder' => 'http://', 'description' => __( 'Enter the external URL to the product.', 'woocommerce' ) ) );
 148 
 149                     // Button text
 150                     woocommerce_wp_text_input( array( 'id' => '_button_text', 'label' => __( 'Button text', 'woocommerce' ), 'placeholder' => _x('Buy product', 'placeholder', 'woocommerce'), 'description' => __( 'This text will be shown on the button linking to the external product.', 'woocommerce' ) ) );
 151 
 152                 echo '</div>';
 153 
 154                 echo '<div class="options_group pricing show_if_simple show_if_external">';
 155 
 156                     // Price
 157                     woocommerce_wp_text_input( array( 'id' => '_regular_price', 'label' => __( 'Regular Price', 'woocommerce' ) . ' (' . get_woocommerce_currency_symbol() . ')', 'data_type' => 'price' ) );
 158 
 159                     // Special Price
 160                     woocommerce_wp_text_input( array( 'id' => '_sale_price', 'data_type' => 'price', 'label' => __( 'Sale Price', 'woocommerce' ) . ' ('.get_woocommerce_currency_symbol().')', 'description' => '<a href="#" class="sale_schedule">' . __( 'Schedule', 'woocommerce' ) . '</a>' ) );
 161 
 162                     // Special Price date range
 163                     $sale_price_dates_from  = ( $date = get_post_meta( $thepostid, '_sale_price_dates_from', true ) ) ? date_i18n( 'Y-m-d', $date ) : '';
 164                     $sale_price_dates_to    = ( $date = get_post_meta( $thepostid, '_sale_price_dates_to', true ) ) ? date_i18n( 'Y-m-d', $date ) : '';
 165 
 166                     echo '  <p class="form-field sale_price_dates_fields">
 167                                 <label for="_sale_price_dates_from">' . __( 'Sale Price Dates', 'woocommerce' ) . '</label>
 168                                 <input type="text" class="short" name="_sale_price_dates_from" id="_sale_price_dates_from" value="' . esc_attr( $sale_price_dates_from ) . '" placeholder="' . _x( 'From&hellip;', 'placeholder', 'woocommerce' ) . ' YYYY-MM-DD" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />
 169                                 <input type="text" class="short" name="_sale_price_dates_to" id="_sale_price_dates_to" value="' . esc_attr( $sale_price_dates_to ) . '" placeholder="' . _x( 'To&hellip;', 'placeholder', 'woocommerce' ) . '  YYYY-MM-DD" maxlength="10" pattern="[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|1[0-9]|2[0-9]|3[01])" />
 170                                 <a href="#" class="cancel_sale_schedule">'. __( 'Cancel', 'woocommerce' ) .'</a>
 171                             </p>';
 172 
 173                     do_action( 'woocommerce_product_options_pricing' );
 174 
 175                 echo '</div>';
 176 
 177                 echo '<div class="options_group show_if_downloadable">';
 178 
 179                     ?>
 180                     <div class="form-field downloadable_files">
 181                         <label><?php _e( 'Downloadable Files', 'woocommerce' ); ?>:</label>
 182                         <table class="widefat">
 183                             <thead>
 184                                 <tr>
 185                                     <th class="sort">&nbsp;</th>
 186                                     <th><?php _e( 'Name', 'woocommerce' ); ?> <span class="tips" data-tip="<?php _e( 'This is the name of the download shown to the customer.', 'woocommerce' ); ?>">[?]</span></th>
 187                                     <th colspan="2"><?php _e( 'File URL', 'woocommerce' ); ?> <span class="tips" data-tip="<?php _e( 'This is the URL or absolute path to the file which customers will get access to.', 'woocommerce' ); ?>">[?]</span></th>
 188                                     <th>&nbsp;</th>
 189                                 </tr>
 190                             </thead>
 191                             <tfoot>
 192                                 <tr>
 193                                     <th colspan="5">
 194                                         <a href="#" class="button insert" data-row="<?php
 195                                             $file = array(
 196                                                 'file' => '',
 197                                                 'name' => ''
 198                                             );
 199                                             ob_start();
 200                                             include( 'views/html-product-download.php' );
 201                                             echo esc_attr( ob_get_clean() );
 202                                         ?>"><?php _e( 'Add File', 'woocommerce' ); ?></a>
 203                                     </th>
 204                                 </tr>
 205                             </tfoot>
 206                             <tbody>
 207                                 <?php
 208                                 $downloadable_files = get_post_meta( $post->ID, '_downloadable_files', true );
 209 
 210                                 if ( $downloadable_files ) {
 211                                     foreach ( $downloadable_files as $key => $file ) {
 212                                         include( 'views/html-product-download.php' );
 213                                     }
 214                                 }
 215                                 ?>
 216                             </tbody>
 217                         </table>
 218                     </div>
 219                     <?php
 220 
 221                     // Download Limit
 222                     woocommerce_wp_text_input( array( 'id' => '_download_limit', 'label' => __( 'Download Limit', 'woocommerce' ), 'placeholder' => __( 'Unlimited', 'woocommerce' ), 'description' => __( 'Leave blank for unlimited re-downloads.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array(
 223                         'step'  => '1',
 224                         'min'   => '0'
 225                     ) ) );
 226 
 227                     // Expirey
 228                     woocommerce_wp_text_input( array( 'id' => '_download_expiry', 'label' => __( 'Download Expiry', 'woocommerce' ), 'placeholder' => __( 'Never', 'woocommerce' ), 'description' => __( 'Enter the number of days before a download link expires, or leave blank.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array(
 229                         'step'  => '1',
 230                         'min'   => '0'
 231                     ) ) );
 232 
 233                      // Download Type
 234                     woocommerce_wp_select( array( 'id' => '_download_type', 'label' => __( 'Download Type', 'woocommerce' ), 'description' => sprintf( __( 'Choose a download type - this controls the <a href="%s">schema</a>.', 'woocommerce' ), 'http://schema.org/' ), 'options' => array(
 235                         ''            => __( 'Standard Product', 'woocommerce' ),
 236                         'application' => __( 'Application/Software', 'woocommerce' ),
 237                         'music'       => __( 'Music', 'woocommerce' ),
 238                     ) ) );
 239 
 240                     do_action( 'woocommerce_product_options_downloads' );
 241 
 242                 echo '</div>';
 243 
 244                 if ( get_option( 'woocommerce_calc_taxes' ) == 'yes' ) {
 245 
 246                     echo '<div class="options_group show_if_simple show_if_external show_if_variable">';
 247 
 248                         // Tax
 249                         woocommerce_wp_select( array( 'id' => '_tax_status', 'label' => __( 'Tax Status', 'woocommerce' ), 'options' => array(
 250                             'taxable'   => __( 'Taxable', 'woocommerce' ),
 251                             'shipping'  => __( 'Shipping only', 'woocommerce' ),
 252                             'none'      => _x( 'None', 'Tax status', 'woocommerce' )
 253                         ) ) );
 254 
 255                         $tax_classes = array_filter( array_map( 'trim', explode( "\n", get_option( 'woocommerce_tax_classes' ) ) ) );
 256                         $classes_options = array();
 257                         $classes_options[''] = __( 'Standard', 'woocommerce' );
 258                         if ( $tax_classes )
 259                             foreach ( $tax_classes as $class )
 260                                 $classes_options[ sanitize_title( $class ) ] = esc_html( $class );
 261 
 262                         woocommerce_wp_select( array( 'id' => '_tax_class', 'label' => __( 'Tax Class', 'woocommerce' ), 'options' => $classes_options ) );
 263 
 264                         do_action( 'woocommerce_product_options_tax' );
 265 
 266                     echo '</div>';
 267 
 268                 }
 269 
 270                 do_action( 'woocommerce_product_options_general_product_data' );
 271                 ?>
 272             </div>
 273 
 274             <div id="inventory_product_data" class="panel woocommerce_options_panel">
 275 
 276                 <?php
 277 
 278                 echo '<div class="options_group">';
 279 
 280                 if (get_option('woocommerce_manage_stock')=='yes') {
 281 
 282                     // manage stock
 283                     woocommerce_wp_checkbox( array( 'id' => '_manage_stock', 'wrapper_class' => 'show_if_simple show_if_variable', 'label' => __('Manage stock?', 'woocommerce' ), 'description' => __( 'Enable stock management at product level (not needed if managing stock at variation level)', 'woocommerce' ) ) );
 284 
 285                     do_action('woocommerce_product_options_stock');
 286 
 287                     echo '<div class="stock_fields show_if_simple show_if_variable">';
 288 
 289                     // Stock
 290                     woocommerce_wp_text_input( array( 'id' => '_stock', 'label' => __( 'Stock Qty', 'woocommerce' ), 'desc_tip' => true, 'description' => __( 'Stock quantity. If this is a variable product this value will be used to control stock for all variations, unless you define stock at variation level.', 'woocommerce' ), 'type' => 'number', 'custom_attributes' => array(
 291                         'step'  => 'any'
 292                     )  ) );
 293 
 294                     do_action('woocommerce_product_options_stock_fields');
 295 
 296                     echo '</div>';
 297 
 298                 }
 299 
 300                 // Stock status
 301                 woocommerce_wp_select( array( 'id' => '_stock_status', 'label' => __( 'Stock status', 'woocommerce' ), 'options' => array(
 302                     'instock' => __( 'In stock', 'woocommerce' ),
 303                     'outofstock' => __( 'Out of stock', 'woocommerce' )
 304                 ), 'desc_tip' => true, 'description' => __( 'Controls whether or not the product is listed as "in stock" or "out of stock" on the frontend.', 'woocommerce' ) ) );
 305 
 306                 if (get_option('woocommerce_manage_stock')=='yes') {
 307 
 308                     echo '<div class="show_if_simple show_if_variable">';
 309 
 310                     // Backorders?
 311                     woocommerce_wp_select( array( 'id' => '_backorders', 'label' => __( 'Allow Backorders?', 'woocommerce' ), 'options' => array(
 312                         'no' => __( 'Do not allow', 'woocommerce' ),
 313                         'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
 314                         'yes' => __( 'Allow', 'woocommerce' )
 315                     ), 'desc_tip' => true, 'description' => __( 'If managing stock, this controls whether or not backorders are allowed for this product and variations. If enabled, stock quantity can go below 0.', 'woocommerce' ) ) );
 316 
 317                     echo '</div>';
 318 
 319                 }
 320                 
 321                 do_action('woocommerce_product_options_stock_status');
 322                 
 323                 echo '</div>';
 324 
 325                 echo '<div class="options_group show_if_simple show_if_variable">';
 326 
 327                 // Individual product
 328                 woocommerce_wp_checkbox( array( 'id' => '_sold_individually', 'wrapper_class' => 'show_if_simple show_if_variable', 'label' => __('Sold Individually', 'woocommerce'), 'description' => __('Enable this to only allow one of this item to be bought in a single order', 'woocommerce') ) );
 329 
 330                 do_action('woocommerce_product_options_sold_individually');
 331 
 332                 echo '</div>';
 333                 
 334                 do_action( 'woocommerce_product_options_inventory_product_data' );
 335                 ?>
 336 
 337             </div>
 338 
 339             <div id="shipping_product_data" class="panel woocommerce_options_panel">
 340 
 341                 <?php
 342 
 343                 echo '<div class="options_group">';
 344 
 345                     // Weight
 346                     if ( wc_product_weight_enabled() )
 347                         woocommerce_wp_text_input( array( 'id' => '_weight', 'label' => __( 'Weight', 'woocommerce' ) . ' (' . get_option('woocommerce_weight_unit') . ')', 'placeholder' => wc_format_localized_decimal( 0 ), 'desc_tip' => 'true', 'description' => __( 'Weight in decimal form', 'woocommerce' ), 'type' => 'text', 'data_type' => 'decimal' ) );
 348 
 349                     // Size fields
 350                     if ( wc_product_dimensions_enabled() ) {
 351                         ?><p class="form-field dimensions_field">
 352                             <label for="product_length"><?php echo __( 'Dimensions', 'woocommerce' ) . ' (' . get_option( 'woocommerce_dimension_unit' ) . ')'; ?></label>
 353                             <span class="wrap">
 354                                 <input id="product_length" placeholder="<?php _e( 'Length', 'woocommerce' ); ?>" class="input-text wc_input_decimal" size="6" type="text" name="_length" value="<?php echo esc_attr( wc_format_localized_decimal( get_post_meta( $thepostid, '_length', true ) ) ); ?>" />
 355                                 <input placeholder="<?php _e( 'Width', 'woocommerce' ); ?>" class="input-text wc_input_decimal" size="6" type="text" name="_width" value="<?php echo esc_attr( wc_format_localized_decimal( get_post_meta( $thepostid, '_width', true ) ) ); ?>" />
 356                                 <input placeholder="<?php _e( 'Height', 'woocommerce' ); ?>" class="input-text wc_input_decimal last" size="6" type="text" name="_height" value="<?php echo esc_attr( wc_format_localized_decimal( get_post_meta( $thepostid, '_height', true ) ) ); ?>" />
 357                             </span>
 358                             <img class="help_tip" data-tip="<?php esc_attr_e( 'LxWxH in decimal form', 'woocommerce' ); ?>" src="<?php echo esc_url( WC()->plugin_url() ); ?>/assets/images/help.png" height="16" width="16" />
 359                         </p><?php
 360                     }
 361 
 362                     do_action( 'woocommerce_product_options_dimensions' );
 363 
 364                 echo '</div>';
 365 
 366                 echo '<div class="options_group">';
 367 
 368                     // Shipping Class
 369                     $classes = get_the_terms( $thepostid, 'product_shipping_class' );
 370                     if ( $classes && ! is_wp_error( $classes ) ) $current_shipping_class = current($classes)->term_id; else $current_shipping_class = '';
 371 
 372                     $args = array(
 373                         'taxonomy'          => 'product_shipping_class',
 374                         'hide_empty'        => 0,
 375                         'show_option_none'  => __( 'No shipping class', 'woocommerce' ),
 376                         'name'              => 'product_shipping_class',
 377                         'id'                => 'product_shipping_class',
 378                         'selected'          => $current_shipping_class,
 379                         'class'             => 'select short'
 380                     );
 381                     ?><p class="form-field dimensions_field"><label for="product_shipping_class"><?php _e( 'Shipping class', 'woocommerce' ); ?></label> <?php wp_dropdown_categories( $args ); ?> <img class="help_tip" data-tip="<?php esc_attr_e( 'Shipping classes are used by certain shipping methods to group similar products.', 'woocommerce' ); ?>" src="<?php echo esc_url( WC()->plugin_url() ); ?>/assets/images/help.png" height="16" width="16" /></p><?php
 382 
 383                     do_action( 'woocommerce_product_options_shipping' );
 384 
 385                 echo '</div>';
 386                 ?>
 387 
 388             </div>
 389 
 390             <div id="product_attributes" class="panel wc-metaboxes-wrapper">
 391 
 392                 <p class="toolbar">
 393                     <a href="#" class="close_all"><?php _e( 'Close all', 'woocommerce' ); ?></a><a href="#" class="expand_all"><?php _e( 'Expand all', 'woocommerce' ); ?></a>
 394                 </p>
 395 
 396                 <div class="product_attributes wc-metaboxes">
 397 
 398                     <?php
 399                         // Array of defined attribute taxonomies
 400                         $attribute_taxonomies = wc_get_attribute_taxonomies();
 401 
 402                         // Product attributes - taxonomies and custom, ordered, with visibility and variation attributes set
 403                         $attributes = maybe_unserialize( get_post_meta( $thepostid, '_product_attributes', true ) );
 404 
 405                         $i = -1;
 406 
 407                         // Taxonomies
 408                         if ( $attribute_taxonomies ) {
 409                             foreach ( $attribute_taxonomies as $tax ) {
 410 
 411                                 // Get name of taxonomy we're now outputting (pa_xxx)
 412                                 $attribute_taxonomy_name = wc_attribute_taxonomy_name( $tax->attribute_name );
 413 
 414                                 // Ensure it exists
 415                                 if ( ! taxonomy_exists( $attribute_taxonomy_name ) )
 416                                     continue;
 417 
 418                                 $i++;
 419 
 420                                 // Get product data values for current taxonomy - this contains ordering and visibility data
 421                                 if ( isset( $attributes[ sanitize_title( $attribute_taxonomy_name ) ] ) )
 422                                     $attribute = $attributes[ sanitize_title( $attribute_taxonomy_name ) ];
 423 
 424                                 $position = empty( $attribute['position'] ) ? 0 : absint( $attribute['position'] );
 425 
 426                                 // Get terms of this taxonomy associated with current product
 427                                 $post_terms = wp_get_post_terms( $thepostid, $attribute_taxonomy_name );
 428 
 429                                 // Any set?
 430                                 $has_terms = ( is_wp_error( $post_terms ) || ! $post_terms || sizeof( $post_terms ) == 0 ) ? 0 : 1;
 431                                 ?>
 432                                 <div class="woocommerce_attribute wc-metabox closed taxonomy <?php echo $attribute_taxonomy_name; ?>" rel="<?php echo $position; ?>" <?php if ( ! $has_terms ) echo 'style="display:none"'; ?>>
 433                                     <h3>
 434                                         <button type="button" class="remove_row button"><?php _e( 'Remove', 'woocommerce' ); ?></button>
 435                                         <div class="handlediv" title="<?php _e( 'Click to toggle', 'woocommerce' ); ?>"></div>
 436                                         <strong class="attribute_name"><?php echo apply_filters( 'woocommerce_attribute_label', $tax->attribute_label ? $tax->attribute_label : $tax->attribute_name, $tax->attribute_name ); ?></strong>
 437                                     </h3>
 438                                     <table cellpadding="0" cellspacing="0" class="woocommerce_attribute_data wc-metabox-content">
 439                                         <tbody>
 440                                             <tr>
 441                                                 <td class="attribute_name">
 442                                                     <label><?php _e( 'Name', 'woocommerce' ); ?>:</label>
 443                                                     <strong><?php echo $tax->attribute_label ? $tax->attribute_label : $tax->attribute_name; ?></strong>
 444 
 445                                                     <input type="hidden" name="attribute_names[<?php echo $i; ?>]" value="<?php echo esc_attr( $attribute_taxonomy_name ); ?>" />
 446                                                     <input type="hidden" name="attribute_position[<?php echo $i; ?>]" class="attribute_position" value="<?php echo esc_attr( $position ); ?>" />
 447                                                     <input type="hidden" name="attribute_is_taxonomy[<?php echo $i; ?>]" value="1" />
 448                                                 </td>
 449                                                 <td rowspan="3">
 450                                                     <label><?php _e( 'Value(s)', 'woocommerce' ); ?>:</label>
 451                                                     <?php if ( $tax->attribute_type == "select" ) : ?>
 452                                                         <select multiple="multiple" data-placeholder="<?php _e( 'Select terms', 'woocommerce' ); ?>" class="multiselect attribute_values" name="attribute_values[<?php echo $i; ?>][]">
 453                                                             <?php
 454                                                             $all_terms = get_terms( $attribute_taxonomy_name, 'orderby=name&hide_empty=0' );
 455                                                             if ( $all_terms ) {
 456                                                                 foreach ( $all_terms as $term ) {
 457                                                                     $has_term = has_term( (int) $term->term_id, $attribute_taxonomy_name, $thepostid ) ? 1 : 0;
 458                                                                     echo '<option value="' . esc_attr( $term->slug ) . '" ' . selected( $has_term, 1, false ) . '>' . $term->name . '</option>';
 459                                                                 }
 460                                                             }
 461                                                             ?>
 462                                                         </select>
 463 
 464                                                         <button class="button plus select_all_attributes"><?php _e( 'Select all', 'woocommerce' ); ?></button> <button class="button minus select_no_attributes"><?php _e( 'Select none', 'woocommerce' ); ?></button>
 465 
 466                                                         <button class="button fr plus add_new_attribute" data-attribute="<?php echo $attribute_taxonomy_name; ?>"><?php _e( 'Add new', 'woocommerce' ); ?></button>
 467 
 468                                                     <?php elseif ( $tax->attribute_type == "text" ) : ?>
 469                                                         <input type="text" name="attribute_values[<?php echo $i; ?>]" value="<?php
 470 
 471                                                             // Text attributes should list terms pipe separated
 472                                                             if ( $post_terms ) {
 473                                                                 $values = array();
 474                                                                 foreach ( $post_terms as $term )
 475                                                                     $values[] = $term->name;
 476                                                                 echo esc_attr( implode( ' ' . WC_DELIMITER . ' ', $values ) );
 477                                                             }
 478 
 479                                                         ?>" placeholder="<?php _e( 'Pipe (|) separate terms', 'woocommerce' ); ?>" />
 480                                                     <?php endif; ?>
 481                                                     <?php do_action( 'woocommerce_product_option_terms', $tax, $i ); ?>
 482                                                 </td>
 483                                             </tr>
 484                                             <tr>
 485                                                 <td>
 486                                                     <label><input type="checkbox" class="checkbox" <?php
 487 
 488                                                         if ( isset( $attribute['is_visible'] ) )
 489                                                             checked( $attribute['is_visible'], 1 );
 490                                                         else
 491                                                             checked( apply_filters( 'default_attribute_visibility', false, $tax ), true );
 492 
 493                                                     ?> name="attribute_visibility[<?php echo $i; ?>]" value="1" /> <?php _e( 'Visible on the product page', 'woocommerce' ); ?></label>
 494                                                 </td>
 495                                             </tr>
 496                                             <tr>
 497                                                 <td>
 498                                                     <div class="enable_variation show_if_variable">
 499                                                     <label><input type="checkbox" class="checkbox" <?php
 500 
 501                                                         if ( isset( $attribute['is_variation'] ) )
 502                                                             checked( $attribute['is_variation'], 1 );
 503                                                         else
 504                                                             checked( apply_filters( 'default_attribute_variation', false, $tax ), true );
 505 
 506                                                     ?> name="attribute_variation[<?php echo $i; ?>]" value="1" /> <?php _e( 'Used for variations', 'woocommerce' ); ?></label>
 507                                                     </div>
 508                                                 </td>
 509                                             </tr>
 510                                         </tbody>
 511                                     </table>
 512                                 </div>
 513                                 <?php
 514                             }
 515                         }
 516 
 517                         // Custom Attributes
 518                         if ( ! empty( $attributes ) ) foreach ( $attributes as $attribute ) {
 519                             if ( $attribute['is_taxonomy'] )
 520                                 continue;
 521 
 522                             $i++;
 523 
 524                             $position = empty( $attribute['position'] ) ? 0 : absint( $attribute['position'] );
 525                             ?>
 526                             <div class="woocommerce_attribute wc-metabox closed" rel="<?php echo $position; ?>">
 527                                 <h3>
 528                                     <button type="button" class="remove_row button"><?php _e( 'Remove', 'woocommerce' ); ?></button>
 529                                     <div class="handlediv" title="<?php _e( 'Click to toggle', 'woocommerce' ); ?>"></div>
 530                                     <strong class="attribute_name"><?php echo apply_filters( 'woocommerce_attribute_label', esc_html( $attribute['name'] ), esc_html( $attribute['name'] ) ); ?></strong>
 531                                 </h3>
 532                                 <table cellpadding="0" cellspacing="0" class="woocommerce_attribute_data wc-metabox-content">
 533                                     <tbody>
 534                                         <tr>
 535                                             <td class="attribute_name">
 536                                                 <label><?php _e( 'Name', 'woocommerce' ); ?>:</label>
 537                                                 <input type="text" class="attribute_name" name="attribute_names[<?php echo $i; ?>]" value="<?php echo esc_attr( $attribute['name'] ); ?>" />
 538                                                 <input type="hidden" name="attribute_position[<?php echo $i; ?>]" class="attribute_position" value="<?php echo esc_attr( $position ); ?>" />
 539                                                 <input type="hidden" name="attribute_is_taxonomy[<?php echo $i; ?>]" value="0" />
 540                                             </td>
 541                                             <td rowspan="3">
 542                                                 <label><?php _e( 'Value(s)', 'woocommerce' ); ?>:</label>
 543                                                 <textarea name="attribute_values[<?php echo $i; ?>]" cols="5" rows="5" placeholder="<?php _e( 'Enter some text, or some attributes by pipe (|) separating values.', 'woocommerce' ); ?>"><?php echo esc_textarea( $attribute['value'] ); ?></textarea>
 544                                             </td>
 545                                         </tr>
 546                                         <tr>
 547                                             <td>
 548                                                 <label><input type="checkbox" class="checkbox" <?php checked( $attribute['is_visible'], 1 ); ?> name="attribute_visibility[<?php echo $i; ?>]" value="1" /> <?php _e( 'Visible on the product page', 'woocommerce' ); ?></label>
 549                                             </td>
 550                                         </tr>
 551                                         <tr>
 552                                             <td>
 553                                                 <div class="enable_variation show_if_variable">
 554                                                 <label><input type="checkbox" class="checkbox" <?php checked( $attribute['is_variation'], 1 ); ?> name="attribute_variation[<?php echo $i; ?>]" value="1" /> <?php _e( 'Used for variations', 'woocommerce' ); ?></label>
 555                                                 </div>
 556                                             </td>
 557                                         </tr>
 558                                     </tbody>
 559                                 </table>
 560                             </div>
 561                             <?php
 562                         }
 563                     ?>
 564                 </div>
 565 
 566                 <p class="toolbar">
 567                     <button type="button" class="button button-primary add_attribute"><?php _e( 'Add', 'woocommerce' ); ?></button>
 568                     <select name="attribute_taxonomy" class="attribute_taxonomy">
 569                         <option value=""><?php _e( 'Custom product attribute', 'woocommerce' ); ?></option>
 570                         <?php
 571                             if ( $attribute_taxonomies ) {
 572                                 foreach ( $attribute_taxonomies as $tax ) {
 573                                     $attribute_taxonomy_name = wc_attribute_taxonomy_name( $tax->attribute_name );
 574                                     $label = $tax->attribute_label ? $tax->attribute_label : $tax->attribute_name;
 575                                     echo '<option value="' . esc_attr( $attribute_taxonomy_name ) . '">' . esc_html( $label ) . '</option>';
 576                                 }
 577                             }
 578                         ?>
 579                     </select>
 580 
 581                     <button type="button" class="button save_attributes"><?php _e( 'Save attributes', 'woocommerce' ); ?></button>
 582                 </p>
 583             </div>
 584             <div id="linked_product_data" class="panel woocommerce_options_panel">
 585 
 586                 <div class="options_group">
 587 
 588                 <p class="form-field"><label for="upsell_ids"><?php _e( 'Up-Sells', 'woocommerce' ); ?></label>
 589                 <select id="upsell_ids" name="upsell_ids[]" class="ajax_chosen_select_products" multiple="multiple" data-placeholder="<?php _e( 'Search for a product&hellip;', 'woocommerce' ); ?>">
 590                     <?php
 591                         $upsell_ids = get_post_meta( $post->ID, '_upsell_ids', true );
 592                         $product_ids = ! empty( $upsell_ids ) ? array_map( 'absint',  $upsell_ids ) : null;
 593                         if ( $product_ids ) {
 594                             foreach ( $product_ids as $product_id ) {
 595 
 596                                 $product = get_product( $product_id );
 597 
 598                                 if ( $product )
 599                                     echo '<option value="' . esc_attr( $product_id ) . '" selected="selected">' . esc_html( $product->get_formatted_name() ) . '</option>';
 600                             }
 601                         }
 602                     ?>
 603                 </select> <img class="help_tip" data-tip='<?php _e( 'Up-sells are products which you recommend instead of the currently viewed product, for example, products that are more profitable or better quality or more expensive.', 'woocommerce' ) ?>' src="<?php echo WC()->plugin_url(); ?>/assets/images/help.png" height="16" width="16" /></p>
 604 
 605                 <p class="form-field"><label for="crosssell_ids"><?php _e( 'Cross-Sells', 'woocommerce' ); ?></label>
 606                 <select id="crosssell_ids" name="crosssell_ids[]" class="ajax_chosen_select_products" multiple="multiple" data-placeholder="<?php _e( 'Search for a product&hellip;', 'woocommerce' ); ?>">
 607                     <?php
 608                         $crosssell_ids = get_post_meta( $post->ID, '_crosssell_ids', true );
 609                         $product_ids = ! empty( $crosssell_ids ) ? array_map( 'absint',  $crosssell_ids ) : null;
 610                         if ( $product_ids ) {
 611                             foreach ( $product_ids as $product_id ) {
 612 
 613                                 $product = get_product( $product_id );
 614 
 615                                 if ( $product )
 616                                     echo '<option value="' . esc_attr( $product_id ) . '" selected="selected">' . esc_html( $product->get_formatted_name() ) . '</option>';
 617                             }
 618                         }
 619                     ?>
 620                 </select> <img class="help_tip" data-tip='<?php _e( 'Cross-sells are products which you promote in the cart, based on the current product.', 'woocommerce' ) ?>' src="<?php echo WC()->plugin_url(); ?>/assets/images/help.png" height="16" width="16" /></p>
 621 
 622                 </div>
 623 
 624                 <?php
 625 
 626                 echo '<div class="options_group grouping show_if_simple show_if_external">';
 627 
 628                     // List Grouped products
 629                     $post_parents = array();
 630                     $post_parents[''] = __( 'Choose a grouped product&hellip;', 'woocommerce' );
 631 
 632                     if ( $grouped_term = get_term_by( 'slug', 'grouped', 'product_type' ) ) {
 633 
 634                         $posts_in = array_unique( (array) get_objects_in_term( $grouped_term->term_id, 'product_type' ) );
 635                         if ( sizeof( $posts_in ) > 0 ) {
 636                             $args = array(
 637                                 'post_type'     => 'product',
 638                                 'post_status'   => 'any',
 639                                 'numberposts'   => -1,
 640                                 'orderby'       => 'title',
 641                                 'order'         => 'asc',
 642                                 'post_parent'   => 0,
 643                                 'include'       => $posts_in,
 644                             );
 645                             $grouped_products = get_posts( $args );
 646 
 647                             if ( $grouped_products ) {
 648                                 foreach ( $grouped_products as $product ) {
 649 
 650                                     if ( $product->ID == $post->ID )
 651                                         continue;
 652 
 653                                     $post_parents[ $product->ID ] = $product->post_title;
 654                                 }
 655                             }
 656                         }
 657 
 658                     }
 659 
 660                     woocommerce_wp_select( array( 'id' => 'parent_id', 'label' => __( 'Grouping', 'woocommerce' ), 'value' => absint( $post->post_parent ), 'options' => $post_parents, 'desc_tip' => true, 'description' => __( 'Set this option to make this product part of a grouped product.', 'woocommerce' ) ) );
 661 
 662                     woocommerce_wp_hidden_input( array( 'id' => 'previous_parent_id', 'value' => absint( $post->post_parent ) ) );
 663 
 664                     do_action( 'woocommerce_product_options_grouping' );
 665 
 666                 echo '</div>';
 667                 ?>
 668 
 669                 <?php do_action( 'woocommerce_product_options_related' ); ?>
 670 
 671             </div>
 672 
 673             <div id="advanced_product_data" class="panel woocommerce_options_panel">
 674 
 675                 <?php
 676 
 677                 echo '<div class="options_group hide_if_external">';
 678 
 679                     // Purchase note
 680                     woocommerce_wp_textarea_input(  array( 'id' => '_purchase_note', 'label' => __( 'Purchase Note', 'woocommerce' ), 'desc_tip' => 'true', 'description' => __( 'Enter an optional note to send the customer after purchase.', 'woocommerce' ) ) );
 681 
 682                 echo '</div>';
 683 
 684                 echo '<div class="options_group">';
 685 
 686                     // menu_order
 687                     woocommerce_wp_text_input(  array( 'id' => 'menu_order', 'label' => __( 'Menu order', 'woocommerce' ), 'desc_tip' => 'true', 'description' => __( 'Custom ordering position.', 'woocommerce' ), 'value' => intval( $post->menu_order ), 'type' => 'number', 'custom_attributes' => array(
 688                         'step'  => '1'
 689                     )  ) );
 690 
 691                 echo '</div>';
 692 
 693                 echo '<div class="options_group reviews">';
 694 
 695                     woocommerce_wp_checkbox( array( 'id' => 'comment_status', 'label' => __( 'Enable reviews', 'woocommerce' ), 'cbvalue' => 'open', 'value' => esc_attr( $post->comment_status ) ) );
 696 
 697                     do_action( 'woocommerce_product_options_reviews' );
 698 
 699                 echo '</div>';
 700                 ?>
 701 
 702             </div>
 703 
 704             <?php
 705                 self::output_variations();
 706 
 707                 do_action( 'woocommerce_product_data_panels' );
 708                 do_action( 'woocommerce_product_write_panels' ); // _deprecated
 709             ?>
 710 
 711             <div class="clear"></div>
 712 
 713         </div>
 714         <?php
 715     }
 716 
 717     /**
 718      * Show options for the variable product type
 719      */
 720     public static function output_variations() {
 721         global $post, $woocommerce;
 722 
 723         $attributes = maybe_unserialize( get_post_meta( $post->ID, '_product_attributes', true ) );
 724 
 725         // See if any are set
 726         $variation_attribute_found = false;
 727         if ( $attributes ) foreach( $attributes as $attribute ) {
 728             if ( isset( $attribute['is_variation'] ) ) {
 729                 $variation_attribute_found = true;
 730                 break;
 731             }
 732         }
 733 
 734         // Get tax classes
 735         $tax_classes = array_filter( array_map('trim', explode( "\n", get_option( 'woocommerce_tax_classes' ) ) ) );
 736         $tax_class_options = array();
 737         $tax_class_options[''] = __( 'Standard', 'woocommerce' );
 738         if ( $tax_classes )
 739             foreach ( $tax_classes as $class )
 740                 $tax_class_options[ sanitize_title( $class ) ] = esc_attr( $class );
 741         ?>
 742         <div id="variable_product_options" class="panel wc-metaboxes-wrapper"><div id="variable_product_options_inner">
 743 
 744             <?php if ( ! $variation_attribute_found ) : ?>
 745 
 746                 <div id="message" class="inline woocommerce-message">
 747                     <p><?php _e( 'Before adding variations, add and save some attributes on the <strong>Attributes</strong> tab.', 'woocommerce' ); ?></p>
 748 
 749                     <p class="submit"><a class="button-primary" href="<?php echo esc_url( apply_filters( 'woocommerce_docs_url', 'http://docs.woothemes.com/document/product-variations', 'product-variations' ) ); ?>" target="_blank"><?php _e( 'Learn more', 'woocommerce' ); ?></a></p>
 750                 </div>
 751 
 752             <?php else : ?>
 753 
 754                 <p class="toolbar">
 755                     <a href="#" class="close_all"><?php _e( 'Close all', 'woocommerce' ); ?></a><a href="#" class="expand_all"><?php _e( 'Expand all', 'woocommerce' ); ?></a>
 756                     <select id="field_to_edit">
 757                         <option value=""><?php _e( 'Choose a field to bulk edit&hellip;', 'woocommerce' ); ?></option>
 758                         <optgroup label="<?php esc_attr_e( 'Status', 'woocommerce' ); ?>">
 759                             <option value="toggle_enabled"><?php _e( 'Toggle &quot;Enabled&quot;', 'woocommerce' ); ?></option>
 760                             <option value="toggle_downloadable"><?php _e( 'Toggle &quot;Downloadable&quot;', 'woocommerce' ); ?></option>
 761                             <option value="toggle_virtual"><?php _e( 'Toggle &quot;Virtual&quot;', 'woocommerce' ); ?></option>
 762                             <option value="delete_all"><?php _e( 'Delete all variations', 'woocommerce' ); ?></option>
 763                         </optgroup>
 764                         <optgroup label="<?php esc_attr_e( 'Pricing', 'woocommerce' ); ?>">
 765                             <option value="variable_regular_price"><?php _e( 'Prices', 'woocommerce' ); ?></option>
 766                             <option value="variable_regular_price_increase"><?php _e( 'Prices increase by (fixed amount or %)', 'woocommerce' ); ?></option>
 767                             <option value="variable_regular_price_decrease"><?php _e( 'Prices decrease by (fixed amount or %)', 'woocommerce' ); ?></option>
 768                             <option value="variable_sale_price"><?php _e( 'Sale prices', 'woocommerce' ); ?></option>
 769                             <option value="variable_sale_price_increase"><?php _e( 'Sale prices increase by (fixed amount or %)', 'woocommerce' ); ?></option>
 770                             <option value="variable_sale_price_decrease"><?php _e( 'Sale prices decrease by (fixed amount or %)', 'woocommerce' ); ?></option>
 771                         </optgroup>
 772                         <optgroup label="<?php esc_attr_e( 'Inventory', 'woocommerce' ); ?>">
 773                             <option value="variable_stock"><?php _e( 'Stock', 'woocommerce' ); ?></option>
 774                         </optgroup>
 775                         <optgroup label="<?php esc_attr_e( 'Shipping', 'woocommerce' ); ?>">
 776                             <option value="variable_length"><?php _e( 'Length', 'woocommerce' ); ?></option>
 777                             <option value="variable_width"><?php _e( 'Width', 'woocommerce' ); ?></option>
 778                             <option value="variable_height"><?php _e( 'Height', 'woocommerce' ); ?></option>
 779                             <option value="variable_weight"><?php _e( 'Weight', 'woocommerce' ); ?></option>
 780                         </optgroup>
 781                         <optgroup label="<?php esc_attr_e( 'Downloadable products', 'woocommerce' ); ?>">
 782                             <option value="variable_download_limit"><?php _e( 'Download limit', 'woocommerce' ); ?></option>
 783                             <option value="variable_download_expiry"><?php _e( 'Download Expiry', 'woocommerce' ); ?></option>
 784                         </optgroup>
 785                         <?php do_action( 'woocommerce_variable_product_bulk_edit_actions' ); ?>
 786                     </select>
 787                     <a class="button bulk_edit"><?php _e( 'Go', 'woocommerce' ); ?></a>
 788                 </p>
 789 
 790                 <div class="woocommerce_variations wc-metaboxes">
 791                     <?php
 792                     // Get parent data
 793                     $parent_data = array(
 794                         'id'        => $post->ID,
 795                         'attributes' => $attributes,
 796                         'tax_class_options' => $tax_class_options,
 797                         'sku'       => get_post_meta( $post->ID, '_sku', true ),
 798                         'weight'    => wc_format_localized_decimal( get_post_meta( $post->ID, '_weight', true ) ),
 799                         'length'    => wc_format_localized_decimal( get_post_meta( $post->ID, '_length', true ) ),
 800                         'width'     => wc_format_localized_decimal( get_post_meta( $post->ID, '_width', true ) ),
 801                         'height'    => wc_format_localized_decimal( get_post_meta( $post->ID, '_height', true ) ),
 802                         'tax_class' => get_post_meta( $post->ID, '_tax_class', true )
 803                     );
 804 
 805                     if ( ! $parent_data['weight'] )
 806                         $parent_data['weight'] = wc_format_localized_decimal( 0 );
 807 
 808                     if ( ! $parent_data['length'] )
 809                         $parent_data['length'] = wc_format_localized_decimal( 0 );
 810 
 811                     if ( ! $parent_data['width'] )
 812                         $parent_data['width'] = wc_format_localized_decimal( 0 );
 813 
 814                     if ( ! $parent_data['height'] )
 815                         $parent_data['height'] = wc_format_localized_decimal( 0 );
 816 
 817                     // Get variations
 818                     $args = array(
 819                         'post_type'     => 'product_variation',
 820                         'post_status'   => array( 'private', 'publish' ),
 821                         'numberposts'   => -1,
 822                         'orderby'       => 'menu_order',
 823                         'order'         => 'asc',
 824                         'post_parent'   => $post->ID
 825                     );
 826                     $variations = get_posts( $args );
 827                     $loop = 0;
 828                     if ( $variations ) foreach ( $variations as $variation ) {
 829 
 830                         $variation_id           = absint( $variation->ID );
 831                         $variation_post_status  = esc_attr( $variation->post_status );
 832                         $variation_data         = get_post_meta( $variation_id );
 833                         $variation_data['variation_post_id'] = $variation_id;
 834 
 835                         // Grab shipping classes
 836                         $shipping_classes = get_the_terms( $variation_id, 'product_shipping_class' );
 837                         $shipping_class = ( $shipping_classes && ! is_wp_error( $shipping_classes ) ) ? current( $shipping_classes )->term_id : '';
 838 
 839                         $variation_fields = array(
 840                             '_sku',
 841                             '_stock',
 842                             '_regular_price',
 843                             '_sale_price',
 844                             '_weight',
 845                             '_length',
 846                             '_width',
 847                             '_height',
 848                             '_download_limit',
 849                             '_download_expiry',
 850                             '_downloadable_files',
 851                             '_downloadable',
 852                             '_virtual',
 853                             '_thumbnail_id',
 854                             '_sale_price_dates_from',
 855                             '_sale_price_dates_to'
 856                         );
 857 
 858                         foreach ( $variation_fields as $field )
 859                             $$field = isset( $variation_data[ $field ][0] ) ? maybe_unserialize( $variation_data[ $field ][0] ) : '';
 860 
 861                         $_tax_class = isset( $variation_data['_tax_class'][0] ) ? $variation_data['_tax_class'][0] : null;
 862                         $image_id   = absint( $_thumbnail_id );
 863                         $image      = $image_id ? wp_get_attachment_thumb_url( $image_id ) : '';
 864 
 865                         // Locale formatting
 866                         $_regular_price = wc_format_localized_price( $_regular_price );
 867                         $_sale_price    = wc_format_localized_price( $_sale_price );
 868                         $_weight        = wc_format_localized_decimal( $_weight );
 869                         $_length        = wc_format_localized_decimal( $_length );
 870                         $_width         = wc_format_localized_decimal( $_width );
 871                         $_height        = wc_format_localized_decimal( $_height );
 872 
 873                         include( 'views/html-variation-admin.php' );
 874 
 875                         $loop++;
 876                     }
 877                     ?>
 878                 </div>
 879 
 880                 <p class="toolbar">
 881 
 882                     <button type="button" class="button button-primary add_variation" <?php disabled( $variation_attribute_found, false ); ?>><?php _e( 'Add Variation', 'woocommerce' ); ?></button>
 883 
 884                     <button type="button" class="button link_all_variations" <?php disabled( $variation_attribute_found, false ); ?>><?php _e( 'Link all variations', 'woocommerce' ); ?></button>
 885 
 886                     <strong><?php _e( 'Defaults', 'woocommerce' ); ?>: <span class="tips" data-tip="<?php _e( 'These are the attributes that will be pre-selected on the frontend.', 'woocommerce' ); ?>">[?]</span></strong>
 887                     <?php
 888                         $default_attributes = maybe_unserialize( get_post_meta( $post->ID, '_default_attributes', true ) );
 889                         foreach ( $attributes as $attribute ) {
 890 
 891                             // Only deal with attributes that are variations
 892                             if ( ! $attribute['is_variation'] )
 893                                 continue;
 894 
 895                             // Get current value for variation (if set)
 896                             $variation_selected_value = isset( $default_attributes[ sanitize_title( $attribute['name'] ) ] ) ? $default_attributes[ sanitize_title( $attribute['name'] ) ] : '';
 897 
 898                             // Name will be something like attribute_pa_color
 899                             echo '<select name="default_attribute_' . sanitize_title( $attribute['name'] ) . '"><option value="">' . __( 'No default', 'woocommerce' ) . ' ' . esc_html( wc_attribute_label( $attribute['name'] ) ) . '&hellip;</option>';
 900 
 901                             // Get terms for attribute taxonomy or value if its a custom attribute
 902                             if ( $attribute['is_taxonomy'] ) {
 903 
 904                                 $post_terms = wp_get_post_terms( $post->ID, $attribute['name'] );
 905 
 906                                 foreach ( $post_terms as $term )
 907                                     echo '<option ' . selected( $variation_selected_value, $term->slug, false ) . ' value="' . esc_attr( $term->slug ) . '">' . apply_filters( 'woocommerce_variation_option_name', esc_html( $term->name ) ) . '</option>';
 908 
 909                             } else {
 910 
 911                                 $options = array_map( 'trim', explode( WC_DELIMITER, $attribute['value'] ) );
 912 
 913                                 foreach ( $options as $option )
 914                                     echo '<option ' . selected( sanitize_title( $variation_selected_value ), sanitize_title( $option ), false ) . ' value="' . esc_attr( sanitize_title( $option ) ) . '">' . esc_html( apply_filters( 'woocommerce_variation_option_name', $option ) )  . '</option>';
 915 
 916                             }
 917 
 918                             echo '</select>';
 919                         }
 920                     ?>
 921                 </p>
 922 
 923             <?php endif; ?>
 924         </div></div>
 925         <?php
 926     }
 927 
 928     /**
 929      * Save meta box data
 930      */
 931     public static function save( $post_id, $post ) {
 932         global $wpdb;
 933 
 934         // Add any default post meta
 935         add_post_meta( $post_id, 'total_sales', '0', true );
 936 
 937         // Get types
 938         $product_type       = empty( $_POST['product-type'] ) ? 'simple' : sanitize_title( stripslashes( $_POST['product-type'] ) );
 939         $is_downloadable    = isset( $_POST['_downloadable'] ) ? 'yes' : 'no';
 940         $is_virtual         = isset( $_POST['_virtual'] ) ? 'yes' : 'no';
 941 
 942         // Product type + Downloadable/Virtual
 943         wp_set_object_terms( $post_id, $product_type, 'product_type' );
 944         update_post_meta( $post_id, '_downloadable', $is_downloadable );
 945         update_post_meta( $post_id, '_virtual', $is_virtual );
 946 
 947         // Update post meta
 948         if ( isset( $_POST['_regular_price'] ) )
 949             update_post_meta( $post_id, '_regular_price', ( $_POST['_regular_price'] === '' ) ? '' : wc_format_decimal( $_POST['_regular_price'] ) );
 950         if ( isset( $_POST['_sale_price'] ) )
 951             update_post_meta( $post_id, '_sale_price', ( $_POST['_sale_price'] === '' ? '' : wc_format_decimal( $_POST['_sale_price'] ) ) );
 952         if ( isset( $_POST['_tax_status'] ) ) update_post_meta( $post_id, '_tax_status', stripslashes( $_POST['_tax_status'] ) );
 953         if ( isset( $_POST['_tax_class'] ) ) update_post_meta( $post_id, '_tax_class', stripslashes( $_POST['_tax_class'] ) );
 954         if ( isset( $_POST['_visibility'] ) ) update_post_meta( $post_id, '_visibility', stripslashes( $_POST['_visibility'] ) );
 955         if ( isset( $_POST['_purchase_note'] ) ) update_post_meta( $post_id, '_purchase_note', stripslashes( $_POST['_purchase_note'] ) );
 956         update_post_meta( $post_id, '_featured', isset( $_POST['_featured'] ) ? 'yes' : 'no' );
 957 
 958         // Dimensions
 959         if ( $is_virtual == 'no' ) {
 960 
 961             if ( isset( $_POST['_weight'] ) )
 962                 update_post_meta( $post_id, '_weight', ( $_POST['_weight'] === '' ) ? '' : wc_format_decimal( $_POST['_weight'] ) );
 963 
 964             if ( isset( $_POST['_length'] ) )
 965                 update_post_meta( $post_id, '_length', ( $_POST['_length'] === '' ) ? '' : wc_format_decimal( $_POST['_length'] ) );
 966 
 967             if ( isset( $_POST['_width'] ) )
 968                 update_post_meta( $post_id, '_width', ( $_POST['_width'] === '' ) ? '' : wc_format_decimal( $_POST['_width'] ) );
 969 
 970             if ( isset( $_POST['_height'] ) )
 971                 update_post_meta( $post_id, '_height', ( $_POST['_height'] === '' ) ? '' : wc_format_decimal( $_POST['_height'] ) );
 972 
 973         } else {
 974             update_post_meta( $post_id, '_weight', '' );
 975             update_post_meta( $post_id, '_length', '' );
 976             update_post_meta( $post_id, '_width', '' );
 977             update_post_meta( $post_id, '_height', '' );
 978         }
 979 
 980         // Save shipping class
 981         $product_shipping_class = $_POST['product_shipping_class'] > 0 && $product_type != 'external' ? absint( $_POST['product_shipping_class'] ) : '';
 982         wp_set_object_terms( $post_id, $product_shipping_class, 'product_shipping_class');
 983 
 984         // Unique SKU
 985         $sku                = get_post_meta( $post_id, '_sku', true );
 986         $new_sku            = wc_clean( stripslashes( $_POST['_sku'] ) );
 987 
 988         if ( $new_sku == '' ) {
 989             update_post_meta( $post_id, '_sku', '' );
 990         } elseif ( $new_sku !== $sku ) {
 991             if ( ! empty( $new_sku ) ) {
 992                 if (
 993                     $wpdb->get_var( $wpdb->prepare("
 994                         SELECT $wpdb->posts.ID
 995                         FROM $wpdb->posts
 996                         LEFT JOIN $wpdb->postmeta ON ($wpdb->posts.ID = $wpdb->postmeta.post_id)
 997                         WHERE $wpdb->posts.post_type = 'product'
 998                         AND $wpdb->posts.post_status = 'publish'
 999                         AND $wpdb->postmeta.meta_key = '_sku' AND $wpdb->postmeta.meta_value = '%s'
1000                      ", $new_sku ) )
1001                     ) {
1002 
1003                     WC_Admin_Meta_Boxes::add_error( __( 'Product SKU must be unique.', 'woocommerce' ) );
1004 
1005                 } else {
1006                     update_post_meta( $post_id, '_sku', $new_sku );
1007                 }
1008             } else {
1009                 update_post_meta( $post_id, '_sku', '' );
1010             }
1011         }
1012 
1013         // Save Attributes
1014         $attributes = array();
1015 
1016         if ( isset( $_POST['attribute_names'] ) && isset( $_POST['attribute_values'] ) ) {
1017             $attribute_names  = $_POST['attribute_names'];
1018             $attribute_values = $_POST['attribute_values'];
1019 
1020             if ( isset( $_POST['attribute_visibility'] ) )
1021                 $attribute_visibility = $_POST['attribute_visibility'];
1022 
1023             if ( isset( $_POST['attribute_variation'] ) )
1024                 $attribute_variation = $_POST['attribute_variation'];
1025 
1026             $attribute_is_taxonomy = $_POST['attribute_is_taxonomy'];
1027             $attribute_position = $_POST['attribute_position'];
1028 
1029             $attribute_names_count = sizeof( $attribute_names );
1030 
1031             for ( $i=0; $i < $attribute_names_count; $i++ ) {
1032                 if ( ! $attribute_names[ $i ] )
1033                     continue;
1034 
1035                 $is_visible     = isset( $attribute_visibility[ $i ] ) ? 1 : 0;
1036                 $is_variation   = isset( $attribute_variation[ $i ] ) ? 1 : 0;
1037                 $is_taxonomy    = $attribute_is_taxonomy[ $i ] ? 1 : 0;
1038 
1039                 if ( $is_taxonomy ) {
1040 
1041                     if ( isset( $attribute_values[ $i ] ) ) {
1042 
1043                         // Select based attributes - Format values (posted values are slugs)
1044                         if ( is_array( $attribute_values[ $i ] ) ) {
1045                             $values = array_map( 'sanitize_title', $attribute_values[ $i ] );
1046 
1047                         // Text based attributes - Posted values are term names - don't change to slugs
1048                         } else {
1049                             $values = array_map( 'stripslashes', array_map( 'strip_tags', explode( WC_DELIMITER, $attribute_values[ $i ] ) ) );
1050                         }
1051 
1052                         // Remove empty items in the array
1053                         $values = array_filter( $values, 'strlen' );
1054 
1055                     } else {
1056                         $values = array();
1057                     }
1058 
1059                     // Update post terms
1060                     if ( taxonomy_exists( $attribute_names[ $i ] ) )
1061                         wp_set_object_terms( $post_id, $values, $attribute_names[ $i ] );
1062 
1063                     if ( $values ) {
1064                         // Add attribute to array, but don't set values
1065                         $attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
1066                             'name'          => wc_clean( $attribute_names[ $i ] ),
1067                             'value'         => '',
1068                             'position'      => $attribute_position[ $i ],
1069                             'is_visible'    => $is_visible,
1070                             'is_variation'  => $is_variation,
1071                             'is_taxonomy'   => $is_taxonomy
1072                         );
1073                     }
1074 
1075                 } elseif ( isset( $attribute_values[ $i ] ) ) {
1076 
1077                     // Text based, separate by pipe
1078                     $values = implode( ' ' . WC_DELIMITER . ' ', array_map( 'wc_clean', explode( WC_DELIMITER, $attribute_values[ $i ] ) ) );
1079 
1080                     // Custom attribute - Add attribute to array and set the values
1081                     $attributes[ sanitize_title( $attribute_names[ $i ] ) ] = array(
1082                         'name'          => wc_clean( $attribute_names[ $i ] ),
1083                         'value'         => $values,
1084                         'position'      => $attribute_position[ $i ],
1085                         'is_visible'    => $is_visible,
1086                         'is_variation'  => $is_variation,
1087                         'is_taxonomy'   => $is_taxonomy
1088                     );
1089                 }
1090 
1091              }
1092         }
1093 
1094         if ( ! function_exists( 'attributes_cmp' ) ) {
1095             function attributes_cmp( $a, $b ) {
1096                 if ( $a['position'] == $b['position'] ) return 0;
1097                 return ( $a['position'] < $b['position'] ) ? -1 : 1;
1098             }
1099         }
1100         uasort( $attributes, 'attributes_cmp' );
1101 
1102         update_post_meta( $post_id, '_product_attributes', $attributes );
1103 
1104         // Sales and prices
1105         if ( in_array( $product_type, array( 'variable', 'grouped' ) ) ) {
1106 
1107             // Variable and grouped products have no prices
1108             update_post_meta( $post_id, '_regular_price', '' );
1109             update_post_meta( $post_id, '_sale_price', '' );
1110             update_post_meta( $post_id, '_sale_price_dates_from', '' );
1111             update_post_meta( $post_id, '_sale_price_dates_to', '' );
1112             update_post_meta( $post_id, '_price', '' );
1113 
1114         } else {
1115 
1116             $date_from = isset( $_POST['_sale_price_dates_from'] ) ? $_POST['_sale_price_dates_from'] : '';
1117             $date_to = isset( $_POST['_sale_price_dates_to'] ) ? $_POST['_sale_price_dates_to'] : '';
1118 
1119             // Dates
1120             if ( $date_from )
1121                 update_post_meta( $post_id, '_sale_price_dates_from', strtotime( $date_from ) );
1122             else
1123                 update_post_meta( $post_id, '_sale_price_dates_from', '' );
1124 
1125             if ( $date_to )
1126                 update_post_meta( $post_id, '_sale_price_dates_to', strtotime( $date_to ) );
1127             else
1128                 update_post_meta( $post_id, '_sale_price_dates_to', '' );
1129 
1130             if ( $date_to && ! $date_from )
1131                 update_post_meta( $post_id, '_sale_price_dates_from', strtotime( 'NOW', current_time( 'timestamp' ) ) );
1132 
1133             // Update price if on sale
1134             if ( $_POST['_sale_price'] !== '' && $date_to == '' && $date_from == '' )
1135                 update_post_meta( $post_id, '_price', wc_format_decimal( $_POST['_sale_price'] ) );
1136             else
1137                 update_post_meta( $post_id, '_price', ( $_POST['_regular_price'] === '' ) ? '' : wc_format_decimal( $_POST['_regular_price'] ) );
1138 
1139             if ( $_POST['_sale_price'] !== '' && $date_from && strtotime( $date_from ) < strtotime( 'NOW', current_time( 'timestamp' ) ) )
1140                 update_post_meta( $post_id, '_price', wc_format_decimal( $_POST['_sale_price'] ) );
1141 
1142             if ( $date_to && strtotime( $date_to ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
1143                 update_post_meta( $post_id, '_price', ( $_POST['_regular_price'] === '' ) ? '' : wc_format_decimal( $_POST['_regular_price'] ) );
1144                 update_post_meta( $post_id, '_sale_price_dates_from', '' );
1145                 update_post_meta( $post_id, '_sale_price_dates_to', '' );
1146             }
1147         }
1148 
1149         // Update parent if grouped so price sorting works and stays in sync with the cheapest child
1150         if ( $post->post_parent > 0 || $product_type == 'grouped' || $_POST['previous_parent_id'] > 0 ) {
1151 
1152             $clear_parent_ids = array();
1153 
1154             if ( $post->post_parent > 0 )
1155                 $clear_parent_ids[] = $post->post_parent;
1156 
1157             if ( $product_type == 'grouped' )
1158                 $clear_parent_ids[] = $post_id;
1159 
1160             if ( $_POST['previous_parent_id'] > 0 )
1161                 $clear_parent_ids[] = absint( $_POST['previous_parent_id'] );
1162 
1163             if ( $clear_parent_ids ) {
1164                 foreach( $clear_parent_ids as $clear_id ) {
1165 
1166                     $children_by_price = get_posts( array(
1167                         'post_parent'   => $clear_id,
1168                         'orderby'       => 'meta_value_num',
1169                         'order'         => 'asc',
1170                         'meta_key'      => '_price',
1171                         'posts_per_page'=> 1,
1172                         'post_type'     => 'product',
1173                         'fields'        => 'ids'
1174                     ) );
1175 
1176                     if ( $children_by_price ) {
1177                         foreach ( $children_by_price as $child ) {
1178                             $child_price = get_post_meta( $child, '_price', true );
1179                             update_post_meta( $clear_id, '_price', $child_price );
1180                         }
1181                     }
1182 
1183                     // Clear cache/transients
1184                     wc_delete_product_transients( $clear_id );
1185                 }
1186             }
1187         }
1188 
1189         // Sold Individually
1190         if ( ! empty( $_POST['_sold_individually'] ) ) {
1191             update_post_meta( $post_id, '_sold_individually', 'yes' );
1192         } else {
1193             update_post_meta( $post_id, '_sold_individually', '' );
1194         }
1195 
1196         // Stock Data
1197         if ( get_option('woocommerce_manage_stock') == 'yes' ) {
1198 
1199             if ( $product_type == 'grouped' ) {
1200 
1201                 update_post_meta( $post_id, '_manage_stock', 'no' );
1202                 update_post_meta( $post_id, '_backorders', 'no' );
1203                 update_post_meta( $post_id, '_stock', '' );
1204 
1205                 wc_update_product_stock_status( $post_id, wc_clean( $_POST['_stock_status'] ) );
1206 
1207             } elseif ( $product_type == 'external' ) {
1208 
1209                 update_post_meta( $post_id, '_manage_stock', 'no' );
1210                 update_post_meta( $post_id, '_backorders', 'no' );
1211                 update_post_meta( $post_id, '_stock', '' );
1212 
1213                 wc_update_product_stock_status( $post_id, 'instock' );
1214 
1215             } elseif ( ! empty( $_POST['_manage_stock'] ) ) {
1216 
1217                 update_post_meta( $post_id, '_manage_stock', 'yes' );
1218                 update_post_meta( $post_id, '_backorders', wc_clean( $_POST['_backorders'] ) );
1219 
1220                 wc_update_product_stock_status( $post_id, wc_clean( $_POST['_stock_status'] ) );
1221                 wc_update_product_stock( $post_id, intval( $_POST['_stock'] ) );
1222 
1223             } else {
1224 
1225                 // Don't manage stock
1226                 update_post_meta( $post_id, '_manage_stock', 'no' );
1227                 update_post_meta( $post_id, '_backorders', wc_clean( $_POST['_backorders'] ) );
1228                 update_post_meta( $post_id, '_stock', '' );
1229 
1230                 wc_update_product_stock_status( $post_id, wc_clean( $_POST['_stock_status'] ) );
1231             }
1232 
1233         } else {
1234             wc_update_product_stock_status( $post_id, wc_clean( $_POST['_stock_status'] ) );
1235         }
1236 
1237         // Upsells
1238         if ( isset( $_POST['upsell_ids'] ) ) {
1239             $upsells = array();
1240             $ids = $_POST['upsell_ids'];
1241             foreach ( $ids as $id )
1242                 if ( $id && $id > 0 )
1243                     $upsells[] = $id;
1244 
1245             update_post_meta( $post_id, '_upsell_ids', $upsells );
1246         } else {
1247             delete_post_meta( $post_id, '_upsell_ids' );
1248         }
1249 
1250         // Cross sells
1251         if ( isset( $_POST['crosssell_ids'] ) ) {
1252             $crosssells = array();
1253             $ids = $_POST['crosssell_ids'];
1254             foreach ( $ids as $id )
1255                 if ( $id && $id > 0 )
1256                     $crosssells[] = $id;
1257 
1258             update_post_meta( $post_id, '_crosssell_ids', $crosssells );
1259         } else {
1260             delete_post_meta( $post_id, '_crosssell_ids' );
1261         }
1262 
1263         // Downloadable options
1264         if ( $is_downloadable == 'yes' ) {
1265 
1266             $_download_limit = absint( $_POST['_download_limit'] );
1267             if ( ! $_download_limit ) {
1268                 $_download_limit = ''; // 0 or blank = unlimited
1269             }
1270 
1271             $_download_expiry = absint( $_POST['_download_expiry'] );
1272             if ( ! $_download_expiry ) {
1273                 $_download_expiry = ''; // 0 or blank = unlimited
1274             }
1275 
1276             // file paths will be stored in an array keyed off md5(file path)
1277             $files = array();
1278 
1279             if ( isset( $_POST['_wc_file_urls'] ) ) {
1280                 $file_names    = isset( $_POST['_wc_file_names'] ) ? array_map( 'wc_clean', $_POST['_wc_file_names'] ) : array();
1281                 $file_urls     = isset( $_POST['_wc_file_urls'] ) ? array_map( 'wc_clean', $_POST['_wc_file_urls'] ) : array();
1282                 $file_url_size = sizeof( $file_urls );
1283 
1284                 for ( $i = 0; $i < $file_url_size; $i ++ ) {
1285                     if ( ! empty( $file_urls[ $i ] ) ) {
1286                         $files[ md5( $file_urls[ $i ] ) ] = array(
1287                             'name' => $file_names[ $i ],
1288                             'file' => $file_urls[ $i ]
1289                         );
1290                     }
1291                 }
1292             }
1293 
1294             // grant permission to any newly added files on any existing orders for this product prior to saving
1295             do_action( 'woocommerce_process_product_file_download_paths', $post_id, 0, $files );
1296 
1297             update_post_meta( $post_id, '_downloadable_files', $files );
1298             update_post_meta( $post_id, '_download_limit', $_download_limit );
1299             update_post_meta( $post_id, '_download_expiry', $_download_expiry );
1300 
1301             if ( isset( $_POST['_download_type'] ) )
1302                 update_post_meta( $post_id, '_download_type', wc_clean( $_POST['_download_type'] ) );
1303         }
1304 
1305         // Product url
1306         if ( $product_type == 'external' ) {
1307             if ( isset( $_POST['_product_url'] ) )
1308                 update_post_meta( $post_id, '_product_url', esc_attr( $_POST['_product_url'] ) );
1309             if ( isset( $_POST['_button_text'] ) )
1310                 update_post_meta( $post_id, '_button_text', esc_attr( $_POST['_button_text'] ) );
1311         }
1312 
1313         // Save variations
1314         if ( $product_type == 'variable' )
1315             self::save_variations( $post_id, $post );
1316 
1317         // Do action for product type
1318         do_action( 'woocommerce_process_product_meta_' . $product_type, $post_id );
1319 
1320         // Clear cache/transients
1321         wc_delete_product_transients( $post_id );
1322     }
1323 
1324     /**
1325      * Save meta box data
1326      */
1327     public static function save_variations( $post_id, $post ) {
1328         global $woocommerce, $wpdb;
1329 
1330         $attributes = (array) maybe_unserialize( get_post_meta( $post_id, '_product_attributes', true ) );
1331 
1332         if ( isset( $_POST['variable_sku'] ) ) {
1333 
1334             $variable_post_id                   = $_POST['variable_post_id'];
1335             $variable_sku                       = $_POST['variable_sku'];
1336             $variable_regular_price             = $_POST['variable_regular_price'];
1337             $variable_sale_price                = $_POST['variable_sale_price'];
1338             $upload_image_id                    = $_POST['upload_image_id'];
1339             $variable_download_limit            = $_POST['variable_download_limit'];
1340             $variable_download_expiry           = $_POST['variable_download_expiry'];
1341             $variable_shipping_class            = $_POST['variable_shipping_class'];
1342             $variable_tax_class                 = isset( $_POST['variable_tax_class'] ) ? $_POST['variable_tax_class'] : array();
1343             $variable_menu_order                = $_POST['variation_menu_order'];
1344             $variable_sale_price_dates_from     = $_POST['variable_sale_price_dates_from'];
1345             $variable_sale_price_dates_to       = $_POST['variable_sale_price_dates_to'];
1346 
1347             $variable_weight                    = isset( $_POST['variable_weight'] ) ? $_POST['variable_weight'] : array();
1348             $variable_length                    = isset( $_POST['variable_length'] ) ? $_POST['variable_length'] : array();
1349             $variable_width                     = isset( $_POST['variable_width'] ) ? $_POST['variable_width'] : array();
1350             $variable_height                    = isset( $_POST['variable_height'] ) ? $_POST['variable_height'] : array();
1351             $variable_stock                     = isset( $_POST['variable_stock'] ) ? $_POST['variable_stock'] : array();
1352             $variable_enabled                   = isset( $_POST['variable_enabled'] ) ? $_POST['variable_enabled'] : array();
1353             $variable_is_virtual                = isset( $_POST['variable_is_virtual'] ) ? $_POST['variable_is_virtual'] : array();
1354             $variable_is_downloadable           = isset( $_POST['variable_is_downloadable'] ) ? $_POST['variable_is_downloadable'] : array();
1355 
1356             $max_loop = max( array_keys( $_POST['variable_post_id'] ) );
1357 
1358             for ( $i = 0; $i <= $max_loop; $i ++ ) {
1359 
1360                 if ( ! isset( $variable_post_id[ $i ] ) )
1361                     continue;
1362 
1363                 $variation_id = absint( $variable_post_id[ $i ] );
1364 
1365                 // Virtal/Downloadable
1366                 $is_virtual           = isset( $variable_is_virtual[ $i ] ) ? 'yes' : 'no';
1367                 $is_downloadable      = isset( $variable_is_downloadable[ $i ] ) ? 'yes' : 'no';
1368                 
1369                 // Enabled or disabled
1370                 $post_status          = isset( $variable_enabled[ $i ] ) ? 'publish' : 'private';
1371                 
1372                 // Generate a useful post title
1373                 $variation_post_title = sprintf( __( 'Variation #%s of %s', 'woocommerce' ), absint( $variation_id ), esc_html( get_the_title( $post_id ) ) );
1374 
1375                 // Update or Add post
1376                 if ( ! $variation_id ) {
1377 
1378                     $variation = array(
1379                         'post_title'    => $variation_post_title,
1380                         'post_content'  => '',
1381                         'post_status'   => $post_status,
1382                         'post_author'   => get_current_user_id(),
1383                         'post_parent'   => $post_id,
1384                         'post_type'     => 'product_variation',
1385                         'menu_order'    => $variable_menu_order[ $i ]
1386                     );
1387 
1388                     $variation_id = wp_insert_post( $variation );
1389 
1390                     do_action( 'woocommerce_create_product_variation', $variation_id );
1391 
1392                 } else {
1393 
1394                     $wpdb->update( $wpdb->posts, array( 'post_status' => $post_status, 'post_title' => $variation_post_title, 'menu_order' => $variable_menu_order[ $i ] ), array( 'ID' => $variation_id ) );
1395 
1396                     do_action( 'woocommerce_update_product_variation', $variation_id );
1397 
1398                 }
1399 
1400                 // Only continue if we have a variation ID
1401                 if ( ! $variation_id ) {
1402                     continue;
1403                 }
1404 
1405                 // Update post meta
1406                 update_post_meta( $variation_id, '_sku', wc_clean( $variable_sku[ $i ] ) );
1407                 update_post_meta( $variation_id, '_thumbnail_id', absint( $upload_image_id[ $i ] ) );
1408                 update_post_meta( $variation_id, '_virtual', wc_clean( $is_virtual ) );
1409                 update_post_meta( $variation_id, '_downloadable', wc_clean( $is_downloadable ) );
1410 
1411                 if ( isset( $variable_weight[ $i ] ) )
1412                     update_post_meta( $variation_id, '_weight', ( $variable_weight[ $i ] === '' ) ? '' : wc_format_decimal( $variable_weight[ $i ] ) );
1413                 if ( isset( $variable_length[ $i ] ) )
1414                     update_post_meta( $variation_id, '_length', ( $variable_length[ $i ] === '' ) ? '' : wc_format_decimal( $variable_length[ $i ] ) );
1415                 if ( isset( $variable_width[ $i ] ) )
1416                     update_post_meta( $variation_id, '_width', ( $variable_width[ $i ] === '' ) ? '' : wc_format_decimal( $variable_width[ $i ] ) );
1417                 if ( isset( $variable_height[ $i ] ) )
1418                     update_post_meta( $variation_id, '_height', ( $variable_height[ $i ] === '' ) ? '' : wc_format_decimal( $variable_height[ $i ] ) );
1419 
1420                 // Stock handling
1421                 if ( isset( $variable_stock[$i] ) ) {
1422                     wc_update_product_stock( $variation_id, wc_clean( $variable_stock[ $i ] ) );
1423                 }
1424 
1425                 // Price handling
1426                 $regular_price  = wc_format_decimal( $variable_regular_price[ $i ] );
1427                 $sale_price     = ( $variable_sale_price[ $i ] === '' ? '' : wc_format_decimal( $variable_sale_price[ $i ] ) );
1428                 $date_from      = wc_clean( $variable_sale_price_dates_from[ $i ] );
1429                 $date_to        = wc_clean( $variable_sale_price_dates_to[ $i ] );
1430 
1431                 update_post_meta( $variation_id, '_regular_price', $regular_price );
1432                 update_post_meta( $variation_id, '_sale_price', $sale_price );
1433 
1434                 // Save Dates
1435                 if ( $date_from )
1436                     update_post_meta( $variation_id, '_sale_price_dates_from', strtotime( $date_from ) );
1437                 else
1438                     update_post_meta( $variation_id, '_sale_price_dates_from', '' );
1439 
1440                 if ( $date_to )
1441                     update_post_meta( $variation_id, '_sale_price_dates_to', strtotime( $date_to ) );
1442                 else
1443                     update_post_meta( $variation_id, '_sale_price_dates_to', '' );
1444 
1445                 if ( $date_to && ! $date_from )
1446                     update_post_meta( $variation_id, '_sale_price_dates_from', strtotime( 'NOW', current_time( 'timestamp' ) ) );
1447 
1448                 // Update price if on sale
1449                 if ( $sale_price != '' && $date_to == '' && $date_from == '' )
1450                     update_post_meta( $variation_id, '_price', $sale_price );
1451                 else
1452                     update_post_meta( $variation_id, '_price', $regular_price );
1453 
1454                 if ( $sale_price != '' && $date_from && strtotime( $date_from ) < strtotime( 'NOW', current_time( 'timestamp' ) ) )
1455                     update_post_meta( $variation_id, '_price', $sale_price );
1456 
1457                 if ( $date_to && strtotime( $date_to ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
1458                     update_post_meta( $variation_id, '_price', $regular_price );
1459                     update_post_meta( $variation_id, '_sale_price_dates_from', '' );
1460                     update_post_meta( $variation_id, '_sale_price_dates_to', '' );
1461                 }
1462 
1463                 if ( isset( $variable_tax_class[ $i ] ) && $variable_tax_class[ $i ] !== 'parent' )
1464                     update_post_meta( $variation_id, '_tax_class', wc_clean( $variable_tax_class[ $i ] ) );
1465                 else
1466                     delete_post_meta( $variation_id, '_tax_class' );
1467 
1468                 if ( $is_downloadable == 'yes' ) {
1469                     update_post_meta( $variation_id, '_download_limit', wc_clean( $variable_download_limit[ $i ] ) );
1470                     update_post_meta( $variation_id, '_download_expiry', wc_clean( $variable_download_expiry[ $i ] ) );
1471 
1472                     $files         = array();
1473                     $file_names    = isset( $_POST['_wc_variation_file_names'][ $variation_id ] ) ? array_map( 'wc_clean', $_POST['_wc_variation_file_names'][ $variation_id ] ) : array();
1474                     $file_urls     = isset( $_POST['_wc_variation_file_urls'][ $variation_id ] ) ? array_map( 'wc_clean', $_POST['_wc_variation_file_urls'][ $variation_id ] ) : array();
1475                     $file_url_size = sizeof( $file_urls );
1476 
1477                     for ( $ii = 0; $ii < $file_url_size; $ii ++ ) {
1478                         if ( ! empty( $file_urls[ $ii ] ) )
1479                             $files[ md5( $file_urls[ $ii ] ) ] = array(
1480                                 'name' => $file_names[ $ii ],
1481                                 'file' => $file_urls[ $ii ]
1482                             );
1483                     }
1484 
1485                     // grant permission to any newly added files on any existing orders for this product prior to saving
1486                     do_action( 'woocommerce_process_product_file_download_paths', $post_id, $variation_id, $files );
1487 
1488                     update_post_meta( $variation_id, '_downloadable_files', $files );
1489                 } else {
1490                     update_post_meta( $variation_id, '_download_limit', '' );
1491                     update_post_meta( $variation_id, '_download_expiry', '' );
1492                     update_post_meta( $variation_id, '_downloadable_files', '' );
1493                 }
1494 
1495                 // Save shipping class
1496                 $variable_shipping_class[ $i ] = ! empty( $variable_shipping_class[ $i ] ) ? (int) $variable_shipping_class[ $i ] : '';
1497                 wp_set_object_terms( $variation_id, $variable_shipping_class[ $i ], 'product_shipping_class');
1498 
1499                 // Update taxonomies - don't use wc_clean as it destroys sanitized characters
1500                 $updated_attribute_keys = array();
1501                 foreach ( $attributes as $attribute ) {
1502                     if ( $attribute['is_variation'] ) {
1503                         $attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
1504                         $value         = isset( $_POST[ $attribute_key ][ $i ] ) ? sanitize_title( stripslashes( $_POST[ $attribute_key ][ $i ] ) ) : '';
1505                         $updated_attribute_keys[] = $attribute_key;
1506                         update_post_meta( $variation_id, $attribute_key, $value );
1507                     }
1508                 }
1509 
1510                 // Remove old taxonomies attributes so data is kept up to date - first get attribute key names
1511                 $delete_attribute_keys = $wpdb->get_col( $wpdb->prepare( "SELECT meta_key FROM {$wpdb->postmeta} WHERE meta_key LIKE 'attribute_%%' AND meta_key NOT IN ( '" . implode( "','", $updated_attribute_keys ) . "' ) AND post_id = %d;", $variation_id ) );
1512 
1513                 foreach ( $delete_attribute_keys as $key ) {
1514                     delete_post_meta( $variation_id, $key );
1515                 }
1516 
1517                 do_action( 'woocommerce_save_product_variation', $variation_id, $i );
1518             }
1519         }
1520 
1521         // Update parent if variable so price sorting works and stays in sync with the cheapest child
1522         WC_Product_Variable::sync( $post_id );
1523 
1524         // Update default attribute options setting
1525         $default_attributes = array();
1526 
1527         foreach ( $attributes as $attribute ) {
1528             if ( $attribute['is_variation'] ) {
1529 
1530                 // Don't use wc_clean as it destroys sanitized characters
1531                 if ( isset( $_POST[ 'default_attribute_' . sanitize_title( $attribute['name'] ) ] ) )
1532                     $value = sanitize_title( trim( stripslashes( $_POST[ 'default_attribute_' . sanitize_title( $attribute['name'] ) ] ) ) );
1533                 else
1534                     $value = '';
1535 
1536                 if ( $value )
1537                     $default_attributes[ sanitize_title( $attribute['name'] ) ] = $value;
1538             }
1539         }
1540 
1541         update_post_meta( $post_id, '_default_attributes', $default_attributes );
1542     }
1543 }
1544 
WooCommerce API documentation generated by ApiGen 2.8.0