1 <?php
   2 /**
   3  * Admin functions for the products post type
   4  *
   5  * @author      WooThemes
   6  * @category    Admin
   7  * @package     WooCommerce/Admin/Post Types
   8  * @version     2.1.0
   9  */
  10 
  11 if ( ! defined( 'ABSPATH' ) ) {
  12     exit; // Exit if accessed directly
  13 }
  14 
  15 if ( ! class_exists( 'WC_Admin_CPT' ) ) {
  16     include( 'class-wc-admin-cpt.php' );
  17 }
  18 
  19 if ( ! class_exists( 'WC_Admin_CPT_Product' ) ) :
  20 
  21 /**
  22  * WC_Admin_CPT_Product Class
  23  */
  24 class WC_Admin_CPT_Product extends WC_Admin_CPT {
  25 
  26     /**
  27      * Constructor
  28      */
  29     public function __construct() {
  30         $this->type = 'product';
  31 
  32         // Post title fields
  33         add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 );
  34 
  35         // Featured image text
  36         add_filter( 'gettext', array( $this, 'featured_image_gettext' ) );
  37         add_filter( 'media_view_strings', array( $this, 'media_view_strings' ), 10, 2 );
  38 
  39         // Visibility option
  40         add_action( 'post_submitbox_misc_actions', array( $this, 'product_data_visibility' ) );
  41 
  42         // Before data updates
  43         add_action( 'pre_post_update', array( $this, 'pre_post_update' ) );
  44         add_filter( 'wp_insert_post_data', array( $this, 'wp_insert_post_data' ) );
  45 
  46         // Admin Columns
  47         add_filter( 'manage_edit-product_columns', array( $this, 'edit_columns' ) );
  48         add_action( 'manage_product_posts_custom_column', array( $this, 'custom_columns' ), 2 );
  49         add_filter( 'manage_edit-product_sortable_columns', array( $this, 'custom_columns_sort' ) );
  50         add_filter( 'request', array( $this, 'custom_columns_orderby' ) );
  51 
  52         // Sort link
  53         add_filter( 'views_edit-product', array( $this, 'default_sorting_link' ) );
  54 
  55         // Prouct filtering
  56         add_action( 'restrict_manage_posts', array( $this, 'product_filters' ) );
  57         add_filter( 'parse_query', array( $this, 'product_filters_query' ) );
  58 
  59         // Enhanced search
  60         add_filter( 'posts_search', array( $this, 'product_search' ) );
  61 
  62         // Maintain hierarchy of terms
  63         add_filter( 'wp_terms_checklist_args', array( $this, 'disable_checked_ontop' ) );
  64 
  65         // Bulk / quick edit
  66         add_action( 'bulk_edit_custom_box', array( $this, 'bulk_edit' ), 10, 2 );
  67         add_action( 'quick_edit_custom_box',  array( $this, 'quick_edit' ), 10, 2 );
  68         add_action( 'save_post', array( $this, 'bulk_and_quick_edit_save_post' ), 10, 2 );
  69 
  70         // Uploads
  71         add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
  72         add_action( 'media_upload_downloadable_product', array( $this, 'media_upload_downloadable_product' ) );
  73         add_filter( 'mod_rewrite_rules', array( $this, 'ms_protect_download_rewite_rules' ) );
  74 
  75         // Download permissions
  76         add_action( 'woocommerce_process_product_file_download_paths', array( $this, 'process_product_file_download_paths' ), 10, 3 );
  77 
  78         // Call WC_Admin_CPT constructor
  79         parent::__construct();
  80     }
  81 
  82     /**
  83      * Check if we're editing or adding a product
  84      * @return boolean
  85      */
  86     private function is_editing_product() {
  87         if ( ! empty( $_GET['post_type'] ) && 'product' == $_GET['post_type'] ) {
  88             return true;
  89         }
  90         if ( ! empty( $_GET['post'] ) && 'product' == get_post_type( $_GET['post'] ) ) {
  91             return true;
  92         }
  93         if ( ! empty( $_REQUEST['post_id'] ) && 'product' == get_post_type( $_REQUEST['post_id'] ) ) {
  94             return true;
  95         }
  96         return false;
  97     }
  98 
  99     /**
 100      * Replace 'Featured' when editing a product. Adapted from https://gist.github.com/tw2113/c7fd8da782232ce90176
 101      * @param  string $string string being translated
 102      * @return string after manipulation
 103      */
 104     public function featured_image_gettext( $string = '' ) {
 105         if ( 'Featured Image' == $string && $this->is_editing_product() ) {
 106             $string = __( 'Product Image', 'woocommerce' );
 107         } elseif ( 'Remove featured image' == $string && $this->is_editing_product() ) {
 108             $string = __( 'Remove product image', 'woocommerce' );
 109         } elseif ( 'Set featured image' == $string && $this->is_editing_product() ) {
 110             $string = __( 'Set product image', 'woocommerce' );
 111         }
 112         return $string;
 113     }
 114 
 115     /**
 116      * Change "Featured Image" to "Product Image" throughout media modals.
 117      * @param  array  $strings Array of strings to translate.
 118      * @param  object $post
 119      * @return array
 120      */
 121     public function media_view_strings( $strings = array(), $post = null ) {
 122         if ( is_object( $post ) ) {
 123             if ( 'product' == $post->post_type ) {
 124                 $strings['setFeaturedImageTitle'] = __( 'Set product image', 'woocommerce' );
 125                 $strings['setFeaturedImage']      = __( 'Set product image', 'woocommerce' );
 126             }
 127         }
 128         return $strings;
 129     }
 130 
 131     /**
 132      * Change title boxes in admin.
 133      * @param  string $text
 134      * @param  object $post
 135      * @return string
 136      */
 137     public function enter_title_here( $text, $post ) {
 138         if ( $post->post_type == 'product' ) {
 139             return __( 'Product name', 'woocommerce' );
 140         }
 141 
 142         return $text;
 143     }
 144 
 145     /**
 146      * Output product visibility options.
 147      *
 148      * @access public
 149      * @return void
 150      */
 151     public function product_data_visibility() {
 152         global $post;
 153 
 154         if ( 'product' != $post->post_type ) {
 155             return;
 156         }
 157 
 158         $current_visibility = ( $current_visibility = get_post_meta( $post->ID, '_visibility', true ) ) ? $current_visibility : 'visible';
 159         $current_featured   = ( $current_featured = get_post_meta( $post->ID, '_featured', true ) ) ? $current_featured : 'no';
 160 
 161         $visibility_options = apply_filters( 'woocommerce_product_visibility_options', array(
 162             'visible' => __( 'Catalog/search', 'woocommerce' ),
 163             'catalog' => __( 'Catalog', 'woocommerce' ),
 164             'search'  => __( 'Search', 'woocommerce' ),
 165             'hidden'  => __( 'Hidden', 'woocommerce' )
 166         ) );
 167         ?>
 168         <div class="misc-pub-section" id="catalog-visibility">
 169             <?php _e( 'Catalog visibility:', 'woocommerce' ); ?> <strong id="catalog-visibility-display"><?php
 170                 echo isset( $visibility_options[ $current_visibility ]  ) ? esc_html( $visibility_options[ $current_visibility ] ) : esc_html( $current_visibility );
 171 
 172                 if ( 'yes' == $current_featured ) {
 173                     echo ', ' . __( 'Featured', 'woocommerce' );
 174                 }
 175             ?></strong>
 176 
 177             <a href="#catalog-visibility" class="edit-catalog-visibility hide-if-no-js"><?php _e( 'Edit', 'woocommerce' ); ?></a>
 178 
 179             <div id="catalog-visibility-select" class="hide-if-js">
 180 
 181                 <input type="hidden" name="current_visibility" id="current_visibility" value="<?php echo esc_attr( $current_visibility ); ?>" />
 182                 <input type="hidden" name="current_featured" id="current_featured" value="<?php echo esc_attr( $current_featured ); ?>" />
 183 
 184                 <?php
 185                     echo '<p>' . __( 'Define the loops this product should be visible in. The product will still be accessible directly.', 'woocommerce' ) . '</p>';
 186 
 187                     foreach ( $visibility_options as $name => $label ) {
 188                         echo '<input type="radio" name="_visibility" id="_visibility_' . esc_attr( $name ) . '" value="' . esc_attr( $name ) . '" ' . checked( $current_visibility, $name, false ) . ' data-label="' . esc_attr( $label ) . '" /> <label for="_visibility_' . esc_attr( $name ) . '" class="selectit">' . esc_html( $label ) . '</label><br />';
 189                     }
 190 
 191                     echo '<p>' . __( 'Enable this option to feature this product.', 'woocommerce' ) . '</p>';
 192 
 193                     echo '<input type="checkbox" name="_featured" id="_featured" ' . checked( $current_featured, 'yes', false ) . ' /> <label for="_featured">' . __( 'Featured Product', 'woocommerce' ) . '</label><br />';
 194                 ?>
 195                 <p>
 196                     <a href="#catalog-visibility" class="save-post-visibility hide-if-no-js button"><?php _e( 'OK', 'woocommerce' ); ?></a>
 197                     <a href="#catalog-visibility" class="cancel-post-visibility hide-if-no-js"><?php _e( 'Cancel', 'woocommerce' ); ?></a>
 198                 </p>
 199             </div>
 200         </div>
 201         <?php
 202     }
 203 
 204     /**
 205      * Some functions, like the term recount, require the visibility to be set prior. Lets save that here.
 206      *
 207      * @param int $post_id
 208      */
 209     public function pre_post_update( $post_id ) {
 210         if ( isset( $_POST['_visibility'] ) ) {
 211             update_post_meta( $post_id, '_visibility', stripslashes( $_POST['_visibility'] ) );
 212         }
 213 
 214         if ( isset( $_POST['_stock_status'] ) ) {
 215             wc_update_product_stock_status( $post_id, wc_clean( $_POST['_stock_status'] ) );
 216         }
 217     }
 218 
 219     /**
 220      * Forces certain product data based on the product's type, e.g. grouped products cannot have a parent.
 221      *
 222      * @param array $data
 223      * @return array
 224      */
 225     public function wp_insert_post_data( $data ) {
 226         global $post;
 227 
 228         if ( 'product' == $data['post_type'] && isset( $_POST['product-type'] ) ) {
 229             $product_type = stripslashes( $_POST['product-type'] );
 230             switch ( $product_type ) {
 231                 case 'grouped' :
 232                 case 'variable' :
 233                     $data['post_parent'] = 0;
 234                     break;
 235 
 236                 default :
 237                     break;
 238             }
 239         }
 240 
 241         return $data;
 242     }
 243 
 244     /**
 245      * Change the columns shown in admin.
 246      */
 247     public function edit_columns( $existing_columns ) {
 248 
 249         if ( empty( $existing_columns ) && ! is_array( $existing_columns ) ) {
 250             $existing_columns = array();
 251         }
 252 
 253         unset( $existing_columns['title'], $existing_columns['comments'], $existing_columns['date'] );
 254 
 255         $columns = array();
 256         $columns['cb'] = '<input type="checkbox" />';
 257         $columns['thumb'] = '<span class="wc-image tips" data-tip="' . __( 'Image', 'woocommerce' ) . '">' . __( 'Image', 'woocommerce' ) . '</span>';
 258 
 259         $columns['name'] = __( 'Name', 'woocommerce' );
 260 
 261         if ( wc_product_sku_enabled() ) {
 262             $columns['sku'] = __( 'SKU', 'woocommerce' );
 263         }
 264 
 265         if ( 'yes' == get_option( 'woocommerce_manage_stock' ) ) {
 266             $columns['is_in_stock'] = __( 'Stock', 'woocommerce' );
 267         }
 268 
 269         $columns['price'] = __( 'Price', 'woocommerce' );
 270 
 271         $columns['product_cat'] = __( 'Categories', 'woocommerce' );
 272         $columns['product_tag'] = __( 'Tags', 'woocommerce' );
 273         $columns['featured'] = '<span class="wc-featured tips" data-tip="' . __( 'Featured', 'woocommerce' ) . '">' . __( 'Featured', 'woocommerce' ) . '</span>';
 274         $columns['product_type'] = '<span class="wc-type tips" data-tip="' . __( 'Type', 'woocommerce' ) . '">' . __( 'Type', 'woocommerce' ) . '</span>';
 275         $columns['date'] = __( 'Date', 'woocommerce' );
 276 
 277         return array_merge( $columns, $existing_columns );
 278     }
 279 
 280     /**
 281      * Define our custom columns shown in admin.
 282      * @param  string $column
 283      */
 284     public function custom_columns( $column ) {
 285         global $post, $woocommerce, $the_product;
 286 
 287         if ( empty( $the_product ) || $the_product->id != $post->ID ) {
 288             $the_product = get_product( $post );
 289         }
 290 
 291         switch ( $column ) {
 292             case 'thumb' :
 293                 echo '<a href="' . get_edit_post_link( $post->ID ) . '">' . $the_product->get_image() . '</a>';
 294                 break;
 295             case 'name' :
 296                 $edit_link        = get_edit_post_link( $post->ID );
 297                 $title            = _draft_or_post_title();
 298                 $post_type_object = get_post_type_object( $post->post_type );
 299                 $can_edit_post    = current_user_can( $post_type_object->cap->edit_post, $post->ID );
 300 
 301                 echo '<strong><a class="row-title" href="' . esc_url( $edit_link ) .'">' . $title.'</a>';
 302 
 303                 _post_states( $post );
 304 
 305                 echo '</strong>';
 306 
 307                 if ( $post->post_parent > 0 ) {
 308                     echo '&nbsp;&nbsp;&larr; <a href="'. get_edit_post_link( $post->post_parent ) .'">'. get_the_title( $post->post_parent ) .'</a>';
 309                 }
 310 
 311                 // Excerpt view
 312                 if ( isset( $_GET['mode'] ) && 'excerpt' == $_GET['mode'] ) {
 313                     echo apply_filters( 'the_excerpt', $post->post_excerpt );
 314                 }
 315 
 316                 // Get actions
 317                 $actions = array();
 318 
 319                 $actions['id'] = 'ID: ' . $post->ID;
 320 
 321                 if ( $can_edit_post && 'trash' != $post->post_status ) {
 322                     $actions['edit'] = '<a href="' . get_edit_post_link( $post->ID, true ) . '" title="' . esc_attr( __( 'Edit this item', 'woocommerce' ) ) . '">' . __( 'Edit', 'woocommerce' ) . '</a>';
 323                     $actions['inline hide-if-no-js'] = '<a href="#" class="editinline" title="' . esc_attr( __( 'Edit this item inline', 'woocommerce' ) ) . '">' . __( 'Quick&nbsp;Edit', 'woocommerce' ) . '</a>';
 324                 }
 325                 if ( current_user_can( $post_type_object->cap->delete_post, $post->ID ) ) {
 326                     if ( 'trash' == $post->post_status ) {
 327                         $actions['untrash'] = '<a title="' . esc_attr( __( 'Restore this item from the Trash', 'woocommerce' ) ) . '" href="' . wp_nonce_url( admin_url( sprintf( $post_type_object->_edit_link . '&amp;action=untrash', $post->ID ) ), 'untrash-post_' . $post->ID ) . '">' . __( 'Restore', 'woocommerce' ) . '</a>';
 328                     } elseif ( EMPTY_TRASH_DAYS ) {
 329                         $actions['trash'] = '<a class="submitdelete" title="' . esc_attr( __( 'Move this item to the Trash', 'woocommerce' ) ) . '" href="' . get_delete_post_link( $post->ID ) . '">' . __( 'Trash', 'woocommerce' ) . '</a>';
 330                     }
 331 
 332                     if ( 'trash' == $post->post_status || ! EMPTY_TRASH_DAYS ) {
 333                         $actions['delete'] = '<a class="submitdelete" title="' . esc_attr( __( 'Delete this item permanently', 'woocommerce' ) ) . '" href="' . get_delete_post_link( $post->ID, '', true ) . '">' . __( 'Delete Permanently', 'woocommerce' ) . '</a>';
 334                     }
 335                 }
 336                 if ( $post_type_object->public ) {
 337                     if ( in_array( $post->post_status, array( 'pending', 'draft', 'future' ) ) ) {
 338                         if ( $can_edit_post )
 339                             $actions['view'] = '<a href="' . esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) . '" title="' . esc_attr( sprintf( __( 'Preview &#8220;%s&#8221;', 'woocommerce' ), $title ) ) . '" rel="permalink">' . __( 'Preview', 'woocommerce' ) . '</a>';
 340                     } elseif ( 'trash' != $post->post_status ) {
 341                         $actions['view'] = '<a href="' . get_permalink( $post->ID ) . '" title="' . esc_attr( sprintf( __( 'View &#8220;%s&#8221;', 'woocommerce' ), $title ) ) . '" rel="permalink">' . __( 'View', 'woocommerce' ) . '</a>';
 342                     }
 343                 }
 344 
 345                 $actions = apply_filters( 'post_row_actions', $actions, $post );
 346 
 347                 echo '<div class="row-actions">';
 348 
 349                 $i = 0;
 350                 $action_count = sizeof($actions);
 351 
 352                 foreach ( $actions as $action => $link ) {
 353                     ++$i;
 354                     ( $i == $action_count ) ? $sep = '' : $sep = ' | ';
 355                     echo '<span class="' . $action . '">' . $link . $sep . '</span>';
 356                 }
 357                 echo '</div>';
 358 
 359                 get_inline_data( $post );
 360 
 361                 /* Custom inline data for woocommerce */
 362                 echo '
 363                     <div class="hidden" id="woocommerce_inline_' . $post->ID . '">
 364                         <div class="menu_order">' . $post->menu_order . '</div>
 365                         <div class="sku">' . $the_product->sku . '</div>
 366                         <div class="regular_price">' . $the_product->regular_price . '</div>
 367                         <div class="sale_price">' . $the_product->sale_price . '</div>
 368                         <div class="weight">' . $the_product->weight . '</div>
 369                         <div class="length">' . $the_product->length . '</div>
 370                         <div class="width">' . $the_product->width . '</div>
 371                         <div class="height">' . $the_product->height . '</div>
 372                         <div class="visibility">' . $the_product->visibility . '</div>
 373                         <div class="stock_status">' . $the_product->stock_status . '</div>
 374                         <div class="stock">' . $the_product->stock . '</div>
 375                         <div class="manage_stock">' . $the_product->manage_stock . '</div>
 376                         <div class="featured">' . $the_product->featured . '</div>
 377                         <div class="product_type">' . $the_product->product_type . '</div>
 378                         <div class="product_is_virtual">' . $the_product->virtual . '</div>
 379                         <div class="tax_status">' . $the_product->tax_status . '</div>
 380                         <div class="tax_class">' . $the_product->tax_class . '</div>
 381                         <div class="backorders">' . $the_product->backorders . '</div>
 382                     </div>
 383                 ';
 384 
 385             break;
 386             case 'sku' :
 387                 echo $the_product->get_sku() ? $the_product->get_sku() : '<span class="na">&ndash;</span>';
 388                 break;
 389             case 'product_type' :
 390                 if ( 'grouped' == $the_product->product_type ) {
 391                     echo '<span class="product-type tips grouped" data-tip="' . __( 'Grouped', 'woocommerce' ) . '"></span>';
 392                 } elseif ( 'external' == $the_product->product_type ) {
 393                     echo '<span class="product-type tips external" data-tip="' . __( 'External/Affiliate', 'woocommerce' ) . '"></span>';
 394                 } elseif ( 'simple' == $the_product->product_type ) {
 395 
 396                     if ( $the_product->is_virtual() ) {
 397                         echo '<span class="product-type tips virtual" data-tip="' . __( 'Virtual', 'woocommerce' ) . '"></span>';
 398                     } elseif ( $the_product->is_downloadable() ) {
 399                         echo '<span class="product-type tips downloadable" data-tip="' . __( 'Downloadable', 'woocommerce' ) . '"></span>';
 400                     } else {
 401                         echo '<span class="product-type tips simple" data-tip="' . __( 'Simple', 'woocommerce' ) . '"></span>';
 402                     }
 403 
 404                 } elseif ( 'variable' == $the_product->product_type ) {
 405                     echo '<span class="product-type tips variable" data-tip="' . __( 'Variable', 'woocommerce' ) . '"></span>';
 406                 } else {
 407                     // Assuming that we have other types in future
 408                     echo '<span class="product-type tips ' . $the_product->product_type . '" data-tip="' . ucfirst( $the_product->product_type ) . '"></span>';
 409                 }
 410                 break;
 411             case 'price' :
 412                 echo $the_product->get_price_html() ? $the_product->get_price_html() : '<span class="na">&ndash;</span>';
 413                 break;
 414             case 'product_cat' :
 415             case 'product_tag' :
 416                 if ( ! $terms = get_the_terms( $post->ID, $column ) ) {
 417                     echo '<span class="na">&ndash;</span>';
 418                 } else {
 419                     foreach ( $terms as $term ) {
 420                         $termlist[] = '<a href="' . admin_url( 'edit.php?' . $column . '=' . $term->slug . '&post_type=product' ) . ' ">' . $term->name . '</a>';
 421                     }
 422 
 423                     echo implode( ', ', $termlist );
 424                 }
 425                 break;
 426             case 'featured' :
 427                 $url = wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_feature_product&product_id=' . $post->ID ), 'woocommerce-feature-product' );
 428                 echo '<a href="' . esc_url( $url ) . '" title="'. __( 'Toggle featured', 'woocommerce' ) . '">';
 429                 if ( $the_product->is_featured() ) {
 430                     echo '<span class="wc-featured tips" data-tip="' . __( 'Yes', 'woocommerce' ) . '">' . __( 'Yes', 'woocommerce' ) . '</span>';
 431                 } else {
 432                     echo '<span class="wc-featured not-featured tips" data-tip="' . __( 'No', 'woocommerce' ) . '">' . __( 'No', 'woocommerce' ) . '</span>';
 433                 }
 434                 echo '</a>';
 435                 break;
 436             case 'is_in_stock' :
 437 
 438                 if ( $the_product->is_in_stock() ) {
 439                     echo '<mark class="instock">' . __( 'In stock', 'woocommerce' ) . '</mark>';
 440                 } else {
 441                     echo '<mark class="outofstock">' . __( 'Out of stock', 'woocommerce' ) . '</mark>';
 442                 }
 443 
 444                 if ( $the_product->managing_stock() ) {
 445                     echo ' &times; ' . $the_product->get_total_stock();
 446                 }
 447 
 448                 break;
 449 
 450             default :
 451                 break;
 452         }
 453     }
 454 
 455     /**
 456      * Make product columns sortable
 457      *
 458      * https://gist.github.com/906872
 459      *
 460      * @access public
 461      * @param mixed $columns
 462      * @return array
 463      */
 464     public function custom_columns_sort( $columns ) {
 465         $custom = array(
 466             'price'         => 'price',
 467             'featured'      => 'featured',
 468             'sku'           => 'sku',
 469             'name'          => 'title'
 470         );
 471         return wp_parse_args( $custom, $columns );
 472     }
 473 
 474     /**
 475      * Product column orderby
 476      *
 477      * http://scribu.net/wordpress/custom-sortable-columns.html#comment-4732
 478      *
 479      * @access public
 480      * @param mixed $vars
 481      * @return array
 482      */
 483     public function custom_columns_orderby( $vars ) {
 484         if ( isset( $vars['orderby'] ) ) {
 485             if ( 'price' == $vars['orderby'] ) {
 486                 $vars = array_merge( $vars, array(
 487                     'meta_key'  => '_price',
 488                     'orderby'   => 'meta_value_num'
 489                 ) );
 490             }
 491             if ( 'featured' == $vars['orderby'] ) {
 492                 $vars = array_merge( $vars, array(
 493                     'meta_key'  => '_featured',
 494                     'orderby'   => 'meta_value'
 495                 ) );
 496             }
 497             if ( 'sku' == $vars['orderby'] ) {
 498                 $vars = array_merge( $vars, array(
 499                     'meta_key'  => '_sku',
 500                     'orderby'   => 'meta_value'
 501                 ) );
 502             }
 503         }
 504 
 505         return $vars;
 506     }
 507 
 508     /**
 509      * Product sorting link
 510      *
 511      * Based on Simple Page Ordering by 10up (http://wordpress.org/extend/plugins/simple-page-ordering/)
 512      *
 513      * @param array $views
 514      * @return array
 515      */
 516     public function default_sorting_link( $views ) {
 517         global $post_type, $wp_query;
 518 
 519         if ( ! current_user_can('edit_others_pages') ) {
 520             return $views;
 521         }
 522 
 523         $class            = ( isset( $wp_query->query['orderby'] ) && $wp_query->query['orderby'] == 'menu_order title' ) ? 'current' : '';
 524         $query_string     = remove_query_arg(array( 'orderby', 'order' ));
 525         $query_string     = add_query_arg( 'orderby', urlencode('menu_order title'), $query_string );
 526         $query_string     = add_query_arg( 'order', urlencode('ASC'), $query_string );
 527         $views['byorder'] = '<a href="'. $query_string . '" class="' . esc_attr( $class ) . '">' . __( 'Sort Products', 'woocommerce' ) . '</a>';
 528 
 529         return $views;
 530     }
 531 
 532     /**
 533      * Show a category filter box
 534      */
 535     public function product_filters() {
 536         global $typenow, $wp_query;
 537 
 538         if ( 'product' != $typenow ) {
 539             return;
 540         }
 541 
 542         // Category Filtering
 543         wc_product_dropdown_categories();
 544 
 545         // Type filtering
 546         $terms   = get_terms( 'product_type' );
 547         $output  = '<select name="product_type" id="dropdown_product_type">';
 548         $output .= '<option value="">' . __( 'Show all product types', 'woocommerce' ) . '</option>';
 549 
 550         foreach ( $terms as $term ) {
 551             $output .= '<option value="' . sanitize_title( $term->name ) . '" ';
 552 
 553             if ( isset( $wp_query->query['product_type'] ) ) {
 554                 $output .= selected( $term->slug, $wp_query->query['product_type'], false );
 555             }
 556 
 557             $output .= '>';
 558 
 559             switch ( $term->name ) {
 560                 case 'grouped' :
 561                     $output .= __( 'Grouped product', 'woocommerce' );
 562                     break;
 563                 case 'external' :
 564                     $output .= __( 'External/Affiliate product', 'woocommerce' );
 565                     break;
 566                 case 'variable' :
 567                     $output .= __( 'Variable product', 'woocommerce' );
 568                     break;
 569                 case 'simple' :
 570                     $output .= __( 'Simple product', 'woocommerce' );
 571                     break;
 572                 default :
 573                     // Assuming that we have other types in future
 574                     $output .= ucfirst( $term->name );
 575                     break;
 576             }
 577 
 578             $output .= " ($term->count)</option>";
 579 
 580             if ( 'simple' == $term->name ) {
 581 
 582                 $output .= '<option value="downloadable" ';
 583 
 584                 if ( isset( $wp_query->query['product_type'] ) ) {
 585                     $output .= selected( 'downloadable', $wp_query->query['product_type'], false );
 586                 }
 587 
 588                 $output .= '> &rarr; ' . __( 'Downloadable', 'woocommerce' ) . '</option>';
 589 
 590                 $output .= '<option value="virtual" ';
 591 
 592                 if ( isset( $wp_query->query['product_type'] ) ) {
 593                     $output .= selected( 'virtual', $wp_query->query['product_type'], false );
 594                 }
 595 
 596                 $output .= '> &rarr;  ' . __( 'Virtual', 'woocommerce' ) . '</option>';
 597             }
 598         }
 599 
 600         $output .= '</select>';
 601 
 602         echo apply_filters( 'woocommerce_product_filters', $output );
 603     }
 604 
 605     /**
 606      * Filter the products in admin based on options
 607      *
 608      * @param mixed $query
 609      */
 610     public function product_filters_query( $query ) {
 611         global $typenow, $wp_query;
 612 
 613         if ( 'product' == $typenow ) {
 614 
 615             if ( isset( $query->query_vars['product_type'] ) ) {
 616                 // Subtypes
 617                 if ( 'downloadable' == $query->query_vars['product_type'] ) {
 618                     $query->query_vars['product_type']  = '';
 619                     $query->query_vars['meta_value']    = 'yes';
 620                     $query->query_vars['meta_key']      = '_downloadable';
 621                 } elseif ( 'virtual' == $query->query_vars['product_type'] ) {
 622                     $query->query_vars['product_type']  = '';
 623                     $query->query_vars['meta_value']    = 'yes';
 624                     $query->query_vars['meta_key']      = '_virtual';
 625                 }
 626             }
 627 
 628             // Categories
 629             if ( isset( $_GET['product_cat'] ) && '0' == $_GET['product_cat'] ) {
 630                 $query->query_vars['tax_query'][] = array(
 631                     'taxonomy' => 'product_cat',
 632                     'field'    => 'id',
 633                     'terms'    => get_terms( 'product_cat', array( 'fields' => 'ids' ) ),
 634                     'operator' => 'NOT IN'
 635                 );
 636             }
 637         }
 638     }
 639 
 640     /**
 641      * Search by SKU or ID for products.
 642      * @param string $where
 643      * @return string
 644      */
 645     public function product_search( $where ) {
 646         global $pagenow, $wpdb, $wp;
 647 
 648         if ( 'edit.php' != $pagenow || ! is_search() || ! isset( $wp->query_vars['s'] ) || 'product' != $wp->query_vars['post_type'] ) {
 649             return $where;
 650         }
 651 
 652         $search_ids = array();
 653         $terms      = explode( ',', $wp->query_vars['s'] );
 654 
 655         foreach ( $terms as $term ) {
 656             if ( is_numeric( $term ) ) {
 657                 $search_ids[] = $term;
 658             }
 659             // Attempt to get a SKU
 660             $sku_to_id = $wpdb->get_col( $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key='_sku' AND meta_value LIKE '%%%s%%';", wc_clean( $term ) ) );
 661 
 662             if ( $sku_to_id && sizeof( $sku_to_id ) > 0 ) {
 663                 $search_ids = array_merge( $search_ids, $sku_to_id );
 664             }
 665         }
 666 
 667         $search_ids = array_filter( array_map( 'absint', $search_ids ) );
 668 
 669         if ( sizeof( $search_ids ) > 0 ) {
 670             $where = str_replace( ')))', ") OR ({$wpdb->posts}.ID IN (" . implode( ',', $search_ids ) . "))))", $where );
 671         }
 672 
 673         return $where;
 674     }
 675 
 676     /**
 677      * Maintain term hierarchy when editing a product.
 678      * @param  array $args
 679      * @return array
 680      */
 681     public function disable_checked_ontop( $args ) {
 682         if ( 'product_cat' == $args['taxonomy'] ) {
 683             $args['checked_ontop'] = false;
 684         }
 685 
 686         return $args;
 687     }
 688 
 689     /**
 690      * Custom bulk edit - form
 691      *
 692      * @access public
 693      * @param mixed $column_name
 694      * @param mixed $post_type
 695      */
 696     public function bulk_edit( $column_name, $post_type ) {
 697         if ( 'price' != $column_name || 'product' != $post_type ) {
 698             return;
 699         }
 700 
 701         include( WC()->plugin_path() . '/includes/admin/views/html-bulk-edit-product.php' );
 702     }
 703 
 704     /**
 705      * Custom quick edit - form
 706      *
 707      * @access public
 708      * @param mixed $column_name
 709      * @param mixed $post_type
 710      */
 711     public function quick_edit( $column_name, $post_type ) {
 712         if ( 'price' != $column_name || 'product' != $post_type ) {
 713             return;
 714         }
 715 
 716         include( WC()->plugin_path() . '/includes/admin/views/html-quick-edit-product.php' );
 717     }
 718 
 719     /**
 720      * Quick and bulk edit saving
 721      *
 722      * @access public
 723      * @param int $post_id
 724      * @param WP_Post $post
 725      * @return int
 726      */
 727     public function bulk_and_quick_edit_save_post( $post_id, $post ) {
 728         // If this is an autosave, our form has not been submitted, so we don't want to do anything.
 729         if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
 730             return $post_id;
 731         }
 732 
 733         // Don't save revisions and autosaves
 734         if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) {
 735             return $post_id;
 736         }
 737 
 738         // Check post type is product
 739         if ( 'product' != $post->post_type ) {
 740             return $post_id;
 741         }
 742 
 743         // Check user permission
 744         if ( ! current_user_can( 'edit_post', $post_id ) ) {
 745             return $post_id;
 746         }
 747 
 748         // Check nonces
 749         if ( ! isset( $_REQUEST['woocommerce_quick_edit_nonce'] ) && ! isset( $_REQUEST['woocommerce_bulk_edit_nonce'] ) ) {
 750             return $post_id;
 751         }
 752         if ( isset( $_REQUEST['woocommerce_quick_edit_nonce'] ) && ! wp_verify_nonce( $_REQUEST['woocommerce_quick_edit_nonce'], 'woocommerce_quick_edit_nonce' ) ) {
 753             return $post_id;
 754         }
 755         if ( isset( $_REQUEST['woocommerce_bulk_edit_nonce'] ) && ! wp_verify_nonce( $_REQUEST['woocommerce_bulk_edit_nonce'], 'woocommerce_bulk_edit_nonce' ) ) {
 756             return $post_id;
 757         }
 758 
 759         // Get the product and save
 760         $product = get_product( $post );
 761 
 762         if ( ! empty( $_REQUEST['woocommerce_quick_edit'] ) ) {
 763             $this->quick_edit_save( $post_id, $product );
 764         } else {
 765             $this->bulk_edit_save( $post_id, $product );
 766         }
 767 
 768         // Clear transient
 769         wc_delete_product_transients( $post_id );
 770 
 771         return $post_id;
 772     }
 773 
 774     /**
 775      * Quick edit
 776      */
 777     private function quick_edit_save( $post_id, $product ) {
 778         global $wpdb;
 779 
 780         $old_regular_price = $product->regular_price;
 781         $old_sale_price    = $product->sale_price;
 782 
 783         // Save fields
 784         if ( isset( $_REQUEST['_sku'] ) ) {
 785             $sku     = get_post_meta( $post_id, '_sku', true );
 786             $new_sku = wc_clean( stripslashes( $_REQUEST['_sku'] ) );
 787 
 788             if ( $new_sku !== $sku ) {
 789                 if ( ! empty( $new_sku ) ) {
 790                         $sku_exists = $wpdb->get_var( $wpdb->prepare("
 791                             SELECT $wpdb->posts.ID
 792                             FROM $wpdb->posts
 793                             LEFT JOIN $wpdb->postmeta ON ($wpdb->posts.ID = $wpdb->postmeta.post_id)
 794                             WHERE $wpdb->posts.post_type = 'product'
 795                             AND $wpdb->posts.post_status = 'publish'
 796                             AND $wpdb->postmeta.meta_key = '_sku' AND $wpdb->postmeta.meta_value = '%s'
 797                          ", $new_sku ) );
 798 
 799                     if ( ! $sku_exists ) {
 800                         update_post_meta( $post_id, '_sku', $new_sku );
 801                     }
 802                 } else {
 803                     update_post_meta( $post_id, '_sku', '' );
 804                 }
 805             }
 806         }
 807 
 808         if ( isset( $_REQUEST['_weight'] ) ) {
 809             update_post_meta( $post_id, '_weight', wc_clean( $_REQUEST['_weight'] ) );
 810         }
 811 
 812         if ( isset( $_REQUEST['_length'] ) ) {
 813             update_post_meta( $post_id, '_length', wc_clean( $_REQUEST['_length'] ) );
 814         }
 815 
 816         if ( isset( $_REQUEST['_width'] ) ) {
 817             update_post_meta( $post_id, '_width', wc_clean( $_REQUEST['_width'] ) );
 818         }
 819 
 820         if ( isset( $_REQUEST['_height'] ) ) {
 821             update_post_meta( $post_id, '_height', wc_clean( $_REQUEST['_height'] ) );
 822         }
 823 
 824         if ( isset( $_REQUEST['_visibility'] ) ) {
 825             update_post_meta( $post_id, '_visibility', wc_clean( $_REQUEST['_visibility'] ) );
 826         }
 827 
 828         if ( isset( $_REQUEST['_featured'] ) ) {
 829             update_post_meta( $post_id, '_featured', 'yes' );
 830         } else {
 831             update_post_meta( $post_id, '_featured', 'no' );
 832         }
 833 
 834         if ( isset( $_REQUEST['_tax_status'] ) ) {
 835             update_post_meta( $post_id, '_tax_status', wc_clean( $_REQUEST['_tax_status'] ) );
 836         }
 837 
 838         if ( isset( $_REQUEST['_tax_class'] ) ) {
 839             update_post_meta( $post_id, '_tax_class', wc_clean( $_REQUEST['_tax_class'] ) );
 840         }
 841 
 842         if ( $product->is_type('simple') || $product->is_type('external') ) {
 843 
 844             if ( isset( $_REQUEST['_regular_price'] ) ) {
 845                 $new_regular_price = $_REQUEST['_regular_price'] === '' ? '' : wc_format_decimal( $_REQUEST['_regular_price'] );
 846                 update_post_meta( $post_id, '_regular_price', $new_regular_price );
 847             } else {
 848                 $new_regular_price = null;
 849             }
 850             if ( isset( $_REQUEST['_sale_price'] ) ) {
 851                 $new_sale_price = $_REQUEST['_sale_price'] === '' ? '' : wc_format_decimal( $_REQUEST['_sale_price'] );
 852                 update_post_meta( $post_id, '_sale_price', $new_sale_price );
 853             } else {
 854                 $new_sale_price = null;
 855             }
 856 
 857             // Handle price - remove dates and set to lowest
 858             $price_changed = false;
 859 
 860             if ( ! is_null( $new_regular_price ) && $new_regular_price != $old_regular_price ) {
 861                 $price_changed = true;
 862             } elseif ( ! is_null( $new_sale_price ) && $new_sale_price != $old_sale_price ) {
 863                 $price_changed = true;
 864             }
 865 
 866             if ( $price_changed ) {
 867                 update_post_meta( $post_id, '_sale_price_dates_from', '' );
 868                 update_post_meta( $post_id, '_sale_price_dates_to', '' );
 869 
 870                 if ( ! is_null( $new_sale_price ) && $new_sale_price !== '' ) {
 871                     update_post_meta( $post_id, '_price', $new_sale_price );
 872                 } else {
 873                     update_post_meta( $post_id, '_price', $new_regular_price );
 874                 }
 875             }
 876         }
 877 
 878         // Handle stock status
 879         if ( isset( $_REQUEST['_stock_status'] ) ) {
 880             wc_update_product_stock_status( $post_id, wc_clean( $_REQUEST['_stock_status'] ) );
 881         }
 882 
 883         // Handle stock
 884         if ( ! $product->is_type('grouped') ) {
 885             if ( isset( $_REQUEST['_manage_stock'] ) ) {
 886                 update_post_meta( $post_id, '_manage_stock', 'yes' );
 887                 wc_update_product_stock( $post_id, intval( $_REQUEST['_stock'] ) );
 888             } else {
 889                 update_post_meta( $post_id, '_manage_stock', 'no' );
 890                 wc_update_product_stock( $post_id, 0 );
 891             }
 892 
 893             if ( ! empty( $_REQUEST['_backorders'] ) ) {
 894                 update_post_meta( $post_id, '_backorders', wc_clean( $_REQUEST['_backorders'] ) );
 895             }
 896         }
 897 
 898         do_action( 'woocommerce_product_quick_edit_save', $product );
 899     }
 900 
 901     /**
 902      * Bulk edit
 903      */
 904     public function bulk_edit_save( $post_id, $product ) {
 905 
 906         $old_regular_price = $product->regular_price;
 907         $old_sale_price    = $product->sale_price;
 908 
 909         // Save fields
 910         if ( ! empty( $_REQUEST['change_weight'] ) && isset( $_REQUEST['_weight'] ) ) {
 911             update_post_meta( $post_id, '_weight', wc_clean( stripslashes( $_REQUEST['_weight'] ) ) );
 912         }
 913 
 914         if ( ! empty( $_REQUEST['change_dimensions'] ) ) {
 915             if ( isset( $_REQUEST['_length'] ) ) {
 916                 update_post_meta( $post_id, '_length', wc_clean( stripslashes( $_REQUEST['_length'] ) ) );
 917             }
 918             if ( isset( $_REQUEST['_width'] ) ) {
 919                 update_post_meta( $post_id, '_width', wc_clean( stripslashes( $_REQUEST['_width'] ) ) );
 920             }
 921             if ( isset( $_REQUEST['_height'] ) ) {
 922                 update_post_meta( $post_id, '_height', wc_clean( stripslashes( $_REQUEST['_height'] ) ) );
 923             }
 924         }
 925 
 926         if ( ! empty( $_REQUEST['_tax_status'] ) ) {
 927             update_post_meta( $post_id, '_tax_status', wc_clean( $_REQUEST['_tax_status'] ) );
 928         }
 929 
 930         if ( ! empty( $_REQUEST['_tax_class'] ) ) {
 931             $tax_class = wc_clean( $_REQUEST['_tax_class'] );
 932             if ( 'standard' == $tax_class ) {
 933                 $tax_class = '';
 934             }
 935             update_post_meta( $post_id, '_tax_class', $tax_class );
 936         }
 937 
 938         if ( ! empty( $_REQUEST['_stock_status'] ) ) {
 939             wc_update_product_stock_status( $post_id, wc_clean( $_REQUEST['_stock_status'] ) );
 940         }
 941 
 942         if ( ! empty( $_REQUEST['_visibility'] ) ) {
 943             update_post_meta( $post_id, '_visibility', stripslashes( $_REQUEST['_visibility'] ) );
 944         }
 945 
 946         if ( ! empty( $_REQUEST['_featured'] ) ) {
 947             update_post_meta( $post_id, '_featured', stripslashes( $_REQUEST['_featured'] ) );
 948         }
 949 
 950         // Handle price - remove dates and set to lowest
 951         if ( $product->is_type( 'simple' ) || $product->is_type( 'external' ) ) {
 952 
 953             $price_changed = false;
 954 
 955             if ( ! empty( $_REQUEST['change_regular_price'] ) ) {
 956 
 957                 $change_regular_price = absint( $_REQUEST['change_regular_price'] );
 958                 $regular_price = esc_attr( stripslashes( $_REQUEST['_regular_price'] ) );
 959 
 960                 switch ( $change_regular_price ) {
 961                     case 1 :
 962                         $new_price = $regular_price;
 963                         break;
 964                     case 2 :
 965                         if ( strstr( $regular_price, '%' ) ) {
 966                             $percent = str_replace( '%', '', $regular_price ) / 100;
 967                             $new_price = $old_regular_price + ( round( $old_regular_price * $percent, absint( get_option( 'woocommerce_price_num_decimals' ) ) ) );
 968                         } else {
 969                             $new_price = $old_regular_price + $regular_price;
 970                         }
 971                         break;
 972                     case 3 :
 973                         if ( strstr( $regular_price, '%' ) ) {
 974                             $percent = str_replace( '%', '', $regular_price ) / 100;
 975                             $new_price = $old_regular_price - ( round ( $old_regular_price * $percent, absint( get_option( 'woocommerce_price_num_decimals' ) ) ) );
 976                         } else {
 977                             $new_price = $old_regular_price - $regular_price;
 978                         }
 979                         break;
 980 
 981                     default :
 982                         break;
 983                 }
 984 
 985                 if ( isset( $new_price ) && $new_price != $old_regular_price ) {
 986                     $price_changed = true;
 987                     $new_price = round( $new_price, absint( get_option( 'woocommerce_price_num_decimals' ) ) );
 988                     update_post_meta( $post_id, '_regular_price', $new_price );
 989                     $product->regular_price = $new_price;
 990                 }
 991             }
 992 
 993             if ( ! empty( $_REQUEST['change_sale_price'] ) ) {
 994 
 995                 $change_sale_price = absint( $_REQUEST['change_sale_price'] );
 996                 $sale_price = esc_attr( stripslashes( $_REQUEST['_sale_price'] ) );
 997 
 998                 switch ( $change_sale_price ) {
 999                     case 1 :
1000                         $new_price = $sale_price;
1001                         break;
1002                     case 2 :
1003                         if ( strstr( $sale_price, '%' ) ) {
1004                             $percent = str_replace( '%', '', $sale_price ) / 100;
1005                             $new_price = $old_sale_price + ( $old_sale_price * $percent );
1006                         } else {
1007                             $new_price = $old_sale_price + $sale_price;
1008                         }
1009                         break;
1010                     case 3 :
1011                         if ( strstr( $sale_price, '%' ) ) {
1012                             $percent = str_replace( '%', '', $sale_price ) / 100;
1013                             $new_price = $old_sale_price - ( $old_sale_price * $percent );
1014                         } else {
1015                             $new_price = $old_sale_price - $sale_price;
1016                         }
1017                         break;
1018                     case 4 :
1019                         if ( strstr( $sale_price, '%' ) ) {
1020                             $percent = str_replace( '%', '', $sale_price ) / 100;
1021                             $new_price = $product->regular_price - ( $product->regular_price * $percent );
1022                         } else {
1023                             $new_price = $product->regular_price - $sale_price;
1024                         }
1025                         break;
1026 
1027                     default :
1028                         break;
1029                 }
1030 
1031                 if ( isset( $new_price ) && $new_price != $old_sale_price ) {
1032                     $price_changed = true;
1033                     $new_price = round( $new_price, absint( get_option( 'woocommerce_price_num_decimals' ) ) );
1034                     update_post_meta( $post_id, '_sale_price', $new_price );
1035                     $product->sale_price = $new_price;
1036                 }
1037             }
1038 
1039             if ( $price_changed ) {
1040                 update_post_meta( $post_id, '_sale_price_dates_from', '' );
1041                 update_post_meta( $post_id, '_sale_price_dates_to', '' );
1042 
1043                 if ( $product->regular_price < $product->sale_price ) {
1044                     $product->sale_price = '';
1045                     update_post_meta( $post_id, '_sale_price', '' );
1046                 }
1047 
1048                 if ( $product->sale_price ) {
1049                     update_post_meta( $post_id, '_price', $product->sale_price );
1050                 } else {
1051                     update_post_meta( $post_id, '_price', $product->regular_price );
1052                 }
1053             }
1054         }
1055 
1056         // Handle stock
1057         if ( ! $product->is_type( 'grouped' ) ) {
1058 
1059             if ( ! empty( $_REQUEST['change_stock'] ) ) {
1060                 update_post_meta( $post_id, '_manage_stock', 'yes' );
1061                 wc_update_product_stock( $post_id, intval( $_REQUEST['_stock'] ) );
1062             }
1063 
1064             if ( ! empty( $_REQUEST['_manage_stock'] ) ) {
1065 
1066                 if ( $_REQUEST['_manage_stock'] == 'yes' ) {
1067                     update_post_meta( $post_id, '_manage_stock', 'yes' );
1068                 } else {
1069                     update_post_meta( $post_id, '_manage_stock', 'no' );
1070                     wc_update_product_stock( $post_id, 0 );
1071                 }
1072             }
1073 
1074             if ( ! empty( $_REQUEST['_backorders'] ) ) {
1075                 update_post_meta( $post_id, '_backorders', wc_clean( $_REQUEST['_backorders'] ) );
1076             }
1077 
1078         }
1079 
1080         do_action( 'woocommerce_product_bulk_edit_save', $product );
1081     }
1082 
1083     /**
1084      * Filter the directory for uploads.
1085      *
1086      * @param array $pathdata
1087      * @return array
1088      */
1089     public function upload_dir( $pathdata ) {
1090         // Change upload dir for downloadable files
1091         if ( isset( $_POST['type'] ) && 'downloadable_product' == $_POST['type'] ) {
1092             if ( empty( $pathdata['subdir'] ) ) {
1093                 $pathdata['path']   = $pathdata['path'] . '/woocommerce_uploads';
1094                 $pathdata['url']    = $pathdata['url']. '/woocommerce_uploads';
1095                 $pathdata['subdir'] = '/woocommerce_uploads';
1096             } else {
1097                 $new_subdir = '/woocommerce_uploads' . $pathdata['subdir'];
1098 
1099                 $pathdata['path']   = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['path'] );
1100                 $pathdata['url']    = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['url'] );
1101                 $pathdata['subdir'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['subdir'] );
1102             }
1103         }
1104 
1105         return $pathdata;
1106     }
1107 
1108     /**
1109      * Run a filter when uploading a downloadable product.
1110      */
1111     public function woocommerce_media_upload_downloadable_product() {
1112         do_action( 'media_upload_file' );
1113     }
1114 
1115     /**
1116      * Protect downloads from ms-files.php in multisite
1117      *
1118      * @param mixed $rewrite
1119      * @return string
1120      */
1121     public function ms_protect_download_rewite_rules( $rewrite ) {
1122         global $wp_rewrite;
1123 
1124         if ( ! is_multisite() || 'redirect' == get_option( 'woocommerce_file_download_method' ) ) {
1125             return $rewrite;
1126         }
1127 
1128         $rule  = "\n# WooCommerce Rules - Protect Files from ms-files.php\n\n";
1129         $rule .= "<IfModule mod_rewrite.c>\n";
1130         $rule .= "RewriteEngine On\n";
1131         $rule .= "RewriteCond %{QUERY_STRING} file=woocommerce_uploads/ [NC]\n";
1132         $rule .= "RewriteRule /ms-files.php$ - [F]\n";
1133         $rule .= "</IfModule>\n\n";
1134 
1135         return $rule . $rewrite;
1136     }
1137 
1138     /**
1139      * Grant downloadable file access to any newly added files on any existing
1140      * orders for this product that have previously been granted downloadable file access
1141      *
1142      * @param int $product_id product identifier
1143      * @param int $variation_id optional product variation identifier
1144      * @param array $downloadable_files newly set files
1145      */
1146     public function process_product_file_download_paths( $product_id, $variation_id, $downloadable_files ) {
1147         global $wpdb;
1148 
1149         if ( $variation_id ) {
1150             $product_id = $variation_id;
1151         }
1152 
1153         $product               = get_product( $product_id );
1154         $existing_download_ids = array_keys( (array) $product->get_files() );
1155         $updated_download_ids  = array_keys( (array) $downloadable_files );
1156 
1157         $new_download_ids      = array_filter( array_diff( $updated_download_ids, $existing_download_ids ) );
1158         $removed_download_ids  = array_filter( array_diff( $existing_download_ids, $updated_download_ids ) );
1159 
1160         if ( $new_download_ids || $removed_download_ids ) {
1161             // determine whether downloadable file access has been granted via the typical order completion, or via the admin ajax method
1162             $existing_permissions = $wpdb->get_results( $wpdb->prepare( "SELECT * from {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE product_id = %d GROUP BY order_id", $product_id ) );
1163 
1164             foreach ( $existing_permissions as $existing_permission ) {
1165                 $order = new WC_Order( $existing_permission->order_id );
1166 
1167                 if ( $order->id ) {
1168                     // Remove permissions
1169                     if ( $removed_download_ids ) {
1170                         foreach ( $removed_download_ids as $download_id ) {
1171                             if ( apply_filters( 'woocommerce_process_product_file_download_paths_remove_access_to_old_file', true, $download_id, $product_id, $order ) ) {
1172                                 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE order_id = %d AND product_id = %d AND download_id = %s", $order->id, $product_id, $download_id ) );
1173                             }
1174                         }
1175                     }
1176                     // Add permissions
1177                     if ( $new_download_ids ) {
1178                         foreach ( $new_download_ids as $download_id ) {
1179                             if ( apply_filters( 'woocommerce_process_product_file_download_paths_grant_access_to_new_file', true, $download_id, $product_id, $order ) ) {
1180                                 // grant permission if it doesn't already exist
1181                                 if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT 1 FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE order_id = %d AND product_id = %d AND download_id = %s", $order->id, $product_id, $download_id ) ) ) {
1182                                     wc_downloadable_file_permission( $download_id, $product_id, $order );
1183                                 }
1184                             }
1185                         }
1186                     }
1187                 }
1188             }
1189         }
1190     }
1191 }
1192 
1193 endif;
1194 
1195 return new WC_Admin_CPT_Product();
1196 
WooCommerce API documentation generated by ApiGen 2.8.0