/includes/admin/class-wc-admin-post-types.php

  1. <?php 
  2. /** 
  3. * Post Types Admin 
  4. * 
  5. * @author WooCommerce 
  6. * @category Admin 
  7. * @package WooCommerce/Admin 
  8. * @version 3.0.0 
  9. */ 
  10.  
  11. if ( ! defined( 'ABSPATH' ) ) { 
  12. exit; 
  13.  
  14. if ( ! class_exists( 'WC_Admin_Post_Types', false ) ) : 
  15.  
  16. /** 
  17. * WC_Admin_Post_Types Class. 
  18. * 
  19. * Handles the edit posts views and some functionality on the edit post screen for WC post types. 
  20. */ 
  21. class WC_Admin_Post_Types { 
  22.  
  23. /** 
  24. * Constructor. 
  25. */ 
  26. public function __construct() { 
  27. add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) ); 
  28. add_filter( 'bulk_post_updated_messages', array( $this, 'bulk_post_updated_messages' ), 10, 2 ); 
  29.  
  30. // Disable Auto Save 
  31. add_action( 'admin_print_scripts', array( $this, 'disable_autosave' ) ); 
  32.  
  33. // Extra post data. 
  34. add_action( 'edit_form_top', array( $this, 'edit_form_top' ) ); 
  35.  
  36. // WP List table columns. Defined here so they are always available for events such as inline editing. 
  37. add_filter( 'manage_product_posts_columns', array( $this, 'product_columns' ) ); 
  38. add_filter( 'manage_shop_coupon_posts_columns', array( $this, 'shop_coupon_columns' ) ); 
  39. add_filter( 'manage_shop_order_posts_columns', array( $this, 'shop_order_columns' ) ); 
  40.  
  41. add_action( 'manage_product_posts_custom_column', array( $this, 'render_product_columns' ), 2 ); 
  42. add_action( 'manage_shop_coupon_posts_custom_column', array( $this, 'render_shop_coupon_columns' ), 2 ); 
  43. add_action( 'manage_shop_order_posts_custom_column', array( $this, 'render_shop_order_columns' ), 2 ); 
  44.  
  45. add_filter( 'manage_edit-product_sortable_columns', array( $this, 'product_sortable_columns' ) ); 
  46. add_filter( 'manage_edit-shop_coupon_sortable_columns', array( $this, 'shop_coupon_sortable_columns' ) ); 
  47. add_filter( 'manage_edit-shop_order_sortable_columns', array( $this, 'shop_order_sortable_columns' ) ); 
  48.  
  49. add_filter( 'list_table_primary_column', array( $this, 'list_table_primary_column' ), 10, 2 ); 
  50. add_filter( 'post_row_actions', array( $this, 'row_actions' ), 2, 100 ); 
  51.  
  52. // Views 
  53. add_filter( 'views_edit-product', array( $this, 'product_sorting_link' ) ); 
  54.  
  55. // Bulk / quick edit 
  56. add_action( 'bulk_edit_custom_box', array( $this, 'bulk_edit' ), 10, 2 ); 
  57. add_action( 'quick_edit_custom_box', array( $this, 'quick_edit' ), 10, 2 ); 
  58. add_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ), 10, 2 ); 
  59. add_action( 'woocommerce_product_bulk_and_quick_edit', array( $this, 'bulk_and_quick_edit_save_post' ), 10, 2 ); 
  60. add_filter( 'bulk_actions-edit-shop_order', array( $this, 'shop_order_bulk_actions' ) ); 
  61. add_filter( 'handle_bulk_actions-edit-shop_order', array( $this, 'handle_shop_order_bulk_actions' ), 10, 3 ); 
  62. add_action( 'admin_notices', array( $this, 'bulk_admin_notices' ) ); 
  63.  
  64. // Order Search 
  65. add_filter( 'get_search_query', array( $this, 'shop_order_search_label' ) ); 
  66. add_filter( 'query_vars', array( $this, 'add_custom_query_var' ) ); 
  67. add_action( 'parse_query', array( $this, 'shop_order_search_custom_fields' ) ); 
  68.  
  69. // Filters 
  70. add_action( 'restrict_manage_posts', array( $this, 'restrict_manage_posts' ) ); 
  71. add_filter( 'request', array( $this, 'request_query' ) ); 
  72. add_filter( 'parse_query', array( $this, 'product_filters_query' ) ); 
  73. add_filter( 'posts_search', array( $this, 'product_search' ) ); 
  74.  
  75. // Edit post screens 
  76. add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 ); 
  77. add_action( 'edit_form_after_title', array( $this, 'edit_form_after_title' ) ); 
  78. add_filter( 'default_hidden_meta_boxes', array( $this, 'hidden_meta_boxes' ), 10, 2 ); 
  79. add_action( 'post_submitbox_misc_actions', array( $this, 'product_data_visibility' ) ); 
  80.  
  81. // Uploads 
  82. add_filter( 'upload_dir', array( $this, 'upload_dir' ) ); 
  83. add_action( 'media_upload_downloadable_product', array( $this, 'media_upload_downloadable_product' ) ); 
  84.  
  85. if ( ! function_exists( 'duplicate_post_plugin_activation' ) ) { 
  86. include( 'class-wc-admin-duplicate-product.php' ); 
  87.  
  88. include_once( dirname( __FILE__ ) . '/class-wc-admin-meta-boxes.php' ); 
  89.  
  90. // Disable DFW feature pointer 
  91. add_action( 'admin_footer', array( $this, 'disable_dfw_feature_pointer' ) ); 
  92.  
  93. // Disable post type view mode options 
  94. add_filter( 'view_mode_post_types', array( $this, 'disable_view_mode_options' ) ); 
  95.  
  96. // Update the screen options. 
  97. add_filter( 'default_hidden_columns', array( $this, 'adjust_shop_order_columns' ), 10, 2 ); 
  98.  
  99. // Show blank state 
  100. add_action( 'manage_posts_extra_tablenav', array( $this, 'maybe_render_blank_state' ) ); 
  101.  
  102. // Hide template for CPT archive. 
  103. add_filter( 'theme_page_templates', array( $this, 'hide_cpt_archive_templates' ), 10, 3 ); 
  104. add_action( 'edit_form_top', array( $this, 'show_cpt_archive_notice' ) ); 
  105.  
  106. /** 
  107. * Adjust shop order columns for the user on certain conditions. 
  108. */ 
  109. public function adjust_shop_order_columns( $hidden, $screen ) { 
  110. if ( isset( $screen->id ) && 'edit-shop_order' === $screen->id ) { 
  111. if ( 'disabled' === get_option( 'woocommerce_ship_to_countries' ) ) { 
  112. $hidden[] = 'shipping_address'; 
  113. } else { 
  114. $hidden[] = 'billing_address'; 
  115. return $hidden; 
  116.  
  117. /** 
  118. * Change messages when a post type is updated. 
  119. * @param array $messages 
  120. * @return array 
  121. */ 
  122. public function post_updated_messages( $messages ) { 
  123. global $post, $post_ID; 
  124.  
  125. $messages['product'] = array( 
  126. 0 => '', // Unused. Messages start at index 1. 
  127. 1 => sprintf( __( 'Product updated. <a href="%s">View Product</a>', 'woocommerce' ), esc_url( get_permalink( $post_ID ) ) ),  
  128. 2 => __( 'Custom field updated.', 'woocommerce' ),  
  129. 3 => __( 'Custom field deleted.', 'woocommerce' ),  
  130. 4 => __( 'Product updated.', 'woocommerce' ),  
  131. /** translators: %s: revision title */ 
  132. 5 => isset( $_GET['revision'] ) ? sprintf( __( 'Product restored to revision from %s', 'woocommerce' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,  
  133. /** translators: %s: product url */ 
  134. 6 => sprintf( __( 'Product published. <a href="%s">View Product</a>', 'woocommerce' ), esc_url( get_permalink( $post_ID ) ) ),  
  135. 7 => __( 'Product saved.', 'woocommerce' ),  
  136. /** translators: %s: product url */ 
  137. 8 => sprintf( __( 'Product submitted. <a target="_blank" href="%s">Preview product</a>', 'woocommerce' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),  
  138. /** translators: 1: date 2: product url */ 
  139. 9 => sprintf( __( 'Product scheduled for: <strong>%1$s</strong>. <a target="_blank" href="%2$s">Preview product</a>', 'woocommerce' ),  
  140. date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ), esc_url( get_permalink( $post_ID ) ) ),  
  141. /** translators: %s: product url */ 
  142. 10 => sprintf( __( 'Product draft updated. <a target="_blank" href="%s">Preview product</a>', 'woocommerce' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post_ID ) ) ) ),  
  143. ); 
  144.  
  145. $messages['shop_order'] = array( 
  146. 0 => '', // Unused. Messages start at index 1. 
  147. 1 => __( 'Order updated.', 'woocommerce' ),  
  148. 2 => __( 'Custom field updated.', 'woocommerce' ),  
  149. 3 => __( 'Custom field deleted.', 'woocommerce' ),  
  150. 4 => __( 'Order updated.', 'woocommerce' ),  
  151. /** translators: %s: revision title */ 
  152. 5 => isset( $_GET['revision'] ) ? sprintf( __( 'Order restored to revision from %s', 'woocommerce' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,  
  153. 6 => __( 'Order updated.', 'woocommerce' ),  
  154. 7 => __( 'Order saved.', 'woocommerce' ),  
  155. 8 => __( 'Order submitted.', 'woocommerce' ),  
  156. /** translators: %s: date */ 
  157. 9 => sprintf( __( 'Order scheduled for: <strong>%1$s</strong>.', 'woocommerce' ),  
  158. date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ) ),  
  159. 10 => __( 'Order draft updated.', 'woocommerce' ),  
  160. 11 => __( 'Order updated and email sent.', 'woocommerce' ),  
  161. ); 
  162.  
  163. $messages['shop_coupon'] = array( 
  164. 0 => '', // Unused. Messages start at index 1. 
  165. 1 => __( 'Coupon updated.', 'woocommerce' ),  
  166. 2 => __( 'Custom field updated.', 'woocommerce' ),  
  167. 3 => __( 'Custom field deleted.', 'woocommerce' ),  
  168. 4 => __( 'Coupon updated.', 'woocommerce' ),  
  169. /** translators: %s: revision title */ 
  170. 5 => isset( $_GET['revision'] ) ? sprintf( __( 'Coupon restored to revision from %s', 'woocommerce' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false,  
  171. 6 => __( 'Coupon updated.', 'woocommerce' ),  
  172. 7 => __( 'Coupon saved.', 'woocommerce' ),  
  173. 8 => __( 'Coupon submitted.', 'woocommerce' ),  
  174. /** translators: %s: date */ 
  175. 9 => sprintf( __( 'Coupon scheduled for: <strong>%1$s</strong>.', 'woocommerce' ),  
  176. date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ) ),  
  177. 10 => __( 'Coupon draft updated.', 'woocommerce' ),  
  178. ); 
  179.  
  180. return $messages; 
  181.  
  182. /** 
  183. * Specify custom bulk actions messages for different post types. 
  184. * @param array $bulk_messages 
  185. * @param array $bulk_counts 
  186. * @return array 
  187. */ 
  188. public function bulk_post_updated_messages( $bulk_messages, $bulk_counts ) { 
  189.  
  190. $bulk_messages['product'] = array( 
  191. /** translators: %s: product count */ 
  192. 'updated' => _n( '%s product updated.', '%s products updated.', $bulk_counts['updated'], 'woocommerce' ),  
  193. /** translators: %s: product count */ 
  194. 'locked' => _n( '%s product not updated, somebody is editing it.', '%s products not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),  
  195. /** translators: %s: product count */ 
  196. 'deleted' => _n( '%s product permanently deleted.', '%s products permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),  
  197. /** translators: %s: product count */ 
  198. 'trashed' => _n( '%s product moved to the Trash.', '%s products moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),  
  199. /** translators: %s: product count */ 
  200. 'untrashed' => _n( '%s product restored from the Trash.', '%s products restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),  
  201. ); 
  202.  
  203. $bulk_messages['shop_order'] = array( 
  204. /** translators: %s: order count */ 
  205. 'updated' => _n( '%s order updated.', '%s orders updated.', $bulk_counts['updated'], 'woocommerce' ),  
  206. /** translators: %s: order count */ 
  207. 'locked' => _n( '%s order not updated, somebody is editing it.', '%s orders not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),  
  208. /** translators: %s: order count */ 
  209. 'deleted' => _n( '%s order permanently deleted.', '%s orders permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),  
  210. /** translators: %s: order count */ 
  211. 'trashed' => _n( '%s order moved to the Trash.', '%s orders moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),  
  212. /** translators: %s: order count */ 
  213. 'untrashed' => _n( '%s order restored from the Trash.', '%s orders restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),  
  214. ); 
  215.  
  216. $bulk_messages['shop_coupon'] = array( 
  217. /** translators: %s: coupon count */ 
  218. 'updated' => _n( '%s coupon updated.', '%s coupons updated.', $bulk_counts['updated'], 'woocommerce' ),  
  219. /** translators: %s: coupon count */ 
  220. 'locked' => _n( '%s coupon not updated, somebody is editing it.', '%s coupons not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),  
  221. /** translators: %s: coupon count */ 
  222. 'deleted' => _n( '%s coupon permanently deleted.', '%s coupons permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),  
  223. /** translators: %s: coupon count */ 
  224. 'trashed' => _n( '%s coupon moved to the Trash.', '%s coupons moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),  
  225. /** translators: %s: coupon count */ 
  226. 'untrashed' => _n( '%s coupon restored from the Trash.', '%s coupons restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),  
  227. ); 
  228.  
  229. return $bulk_messages; 
  230.  
  231. /** 
  232. * Define custom columns for products. 
  233. * @param array $existing_columns 
  234. * @return array 
  235. */ 
  236. public function product_columns( $existing_columns ) { 
  237. if ( empty( $existing_columns ) && ! is_array( $existing_columns ) ) { 
  238. $existing_columns = array(); 
  239.  
  240. unset( $existing_columns['title'], $existing_columns['comments'], $existing_columns['date'] ); 
  241.  
  242. $columns = array(); 
  243. $columns['cb'] = '<input type="checkbox" />'; 
  244. $columns['thumb'] = '<span class="wc-image tips" data-tip="' . esc_attr__( 'Image', 'woocommerce' ) . '">' . __( 'Image', 'woocommerce' ) . '</span>'; 
  245. $columns['name'] = __( 'Name', 'woocommerce' ); 
  246.  
  247. if ( wc_product_sku_enabled() ) { 
  248. $columns['sku'] = __( 'SKU', 'woocommerce' ); 
  249.  
  250. if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { 
  251. $columns['is_in_stock'] = __( 'Stock', 'woocommerce' ); 
  252.  
  253. $columns['price'] = __( 'Price', 'woocommerce' ); 
  254. $columns['product_cat'] = __( 'Categories', 'woocommerce' ); 
  255. $columns['product_tag'] = __( 'Tags', 'woocommerce' ); 
  256. $columns['featured'] = '<span class="wc-featured parent-tips" data-tip="' . esc_attr__( 'Featured', 'woocommerce' ) . '">' . __( 'Featured', 'woocommerce' ) . '</span>'; 
  257. $columns['product_type'] = '<span class="wc-type parent-tips" data-tip="' . esc_attr__( 'Type', 'woocommerce' ) . '">' . __( 'Type', 'woocommerce' ) . '</span>'; 
  258. $columns['date'] = __( 'Date', 'woocommerce' ); 
  259.  
  260. return array_merge( $columns, $existing_columns ); 
  261.  
  262.  
  263. /** 
  264. * Define custom columns for coupons. 
  265. * @param array $existing_columns 
  266. * @return array 
  267. */ 
  268. public function shop_coupon_columns( $existing_columns ) { 
  269. $columns = array(); 
  270. $columns['cb'] = $existing_columns['cb']; 
  271. $columns['coupon_code'] = __( 'Code', 'woocommerce' ); 
  272. $columns['type'] = __( 'Coupon type', 'woocommerce' ); 
  273. $columns['amount'] = __( 'Coupon amount', 'woocommerce' ); 
  274. $columns['description'] = __( 'Description', 'woocommerce' ); 
  275. $columns['products'] = __( 'Product IDs', 'woocommerce' ); 
  276. $columns['usage'] = __( 'Usage / Limit', 'woocommerce' ); 
  277. $columns['expiry_date'] = __( 'Expiry date', 'woocommerce' ); 
  278.  
  279. return $columns; 
  280.  
  281. /** 
  282. * Define custom columns for orders. 
  283. * @param array $existing_columns 
  284. * @return array 
  285. */ 
  286. public function shop_order_columns( $existing_columns ) { 
  287. $columns = array(); 
  288. $columns['cb'] = $existing_columns['cb']; 
  289. $columns['order_status'] = '<span class="status_head tips" data-tip="' . esc_attr__( 'Status', 'woocommerce' ) . '">' . esc_attr__( 'Status', 'woocommerce' ) . '</span>'; 
  290. $columns['order_title'] = __( 'Order', 'woocommerce' ); 
  291. $columns['billing_address'] = __( 'Billing', 'woocommerce' ); 
  292. $columns['shipping_address'] = __( 'Ship to', 'woocommerce' ); 
  293. $columns['customer_message'] = '<span class="notes_head tips" data-tip="' . esc_attr__( 'Customer message', 'woocommerce' ) . '">' . esc_attr__( 'Customer message', 'woocommerce' ) . '</span>'; 
  294. $columns['order_notes'] = '<span class="order-notes_head tips" data-tip="' . esc_attr__( 'Order notes', 'woocommerce' ) . '">' . esc_attr__( 'Order notes', 'woocommerce' ) . '</span>'; 
  295. $columns['order_date'] = __( 'Date', 'woocommerce' ); 
  296. $columns['order_total'] = __( 'Total', 'woocommerce' ); 
  297. $columns['order_actions'] = __( 'Actions', 'woocommerce' ); 
  298.  
  299. return $columns; 
  300.  
  301. /** 
  302. * Ouput custom columns for products. 
  303. * 
  304. * @param string $column 
  305. */ 
  306. public function render_product_columns( $column ) { 
  307. global $post, $the_product; 
  308.  
  309. if ( empty( $the_product ) || $the_product->get_id() != $post->ID ) { 
  310. $the_product = wc_get_product( $post ); 
  311.  
  312. // Only continue if we have a product. 
  313. if ( empty( $the_product ) ) { 
  314. return; 
  315.  
  316. switch ( $column ) { 
  317. case 'thumb' : 
  318. echo '<a href="' . get_edit_post_link( $post->ID ) . '">' . $the_product->get_image( 'thumbnail' ) . '</a>'; 
  319. break; 
  320. case 'name' : 
  321. $edit_link = get_edit_post_link( $post->ID ); 
  322. $title = _draft_or_post_title(); 
  323.  
  324. echo '<strong><a class="row-title" href="' . esc_url( $edit_link ) . '">' . esc_html( $title ) . '</a>'; 
  325.  
  326. _post_states( $post ); 
  327.  
  328. echo '</strong>'; 
  329.  
  330. if ( $post->post_parent > 0 ) { 
  331. echo '  ← <a href="' . get_edit_post_link( $post->post_parent ) . '">' . get_the_title( $post->post_parent ) . '</a>'; 
  332.  
  333. // Excerpt view 
  334. if ( isset( $_GET['mode'] ) && 'excerpt' == $_GET['mode'] ) { 
  335. echo apply_filters( 'the_excerpt', $post->post_excerpt ); 
  336.  
  337. get_inline_data( $post ); 
  338.  
  339. /** Custom inline data for woocommerce. */ 
  340. echo ' 
  341. <div class="hidden" id="woocommerce_inline_' . absint( $post->ID ) . '"> 
  342. <div class="menu_order">' . absint( $the_product->get_menu_order() ) . '</div> 
  343. <div class="sku">' . esc_html( $the_product->get_sku() ) . '</div> 
  344. <div class="regular_price">' . esc_html( $the_product->get_regular_price() ) . '</div> 
  345. <div class="sale_price">' . esc_html( $the_product->get_sale_price() ) . '</div> 
  346. <div class="weight">' . esc_html( $the_product->get_weight() ) . '</div> 
  347. <div class="length">' . esc_html( $the_product->get_length() ) . '</div> 
  348. <div class="width">' . esc_html( $the_product->get_width() ) . '</div> 
  349. <div class="height">' . esc_html( $the_product->get_height() ) . '</div> 
  350. <div class="shipping_class">' . esc_html( $the_product->get_shipping_class() ) . '</div> 
  351. <div class="visibility">' . esc_html( $the_product->get_catalog_visibility() ) . '</div> 
  352. <div class="stock_status">' . esc_html( $the_product->get_stock_status() ) . '</div> 
  353. <div class="stock">' . esc_html( $the_product->get_stock_quantity() ) . '</div> 
  354. <div class="manage_stock">' . esc_html( wc_bool_to_string( $the_product->get_manage_stock() ) ) . '</div> 
  355. <div class="featured">' . esc_html( wc_bool_to_string( $the_product->get_featured() ) ) . '</div> 
  356. <div class="product_type">' . esc_html( $the_product->get_type() ) . '</div> 
  357. <div class="product_is_virtual">' . esc_html( wc_bool_to_string( $the_product->get_virtual() ) ) . '</div> 
  358. <div class="tax_status">' . esc_html( $the_product->get_tax_status() ) . '</div> 
  359. <div class="tax_class">' . esc_html( $the_product->get_tax_class() ) . '</div> 
  360. <div class="backorders">' . esc_html( $the_product->get_backorders() ) . '</div> 
  361. </div> 
  362. '; 
  363.  
  364. break; 
  365. case 'sku' : 
  366. echo $the_product->get_sku() ? esc_html( $the_product->get_sku() ) : '<span class="na">–</span>'; 
  367. break; 
  368. case 'product_type' : 
  369. if ( $the_product->is_type( 'grouped' ) ) { 
  370. echo '<span class="product-type tips grouped" data-tip="' . esc_attr__( 'Grouped', 'woocommerce' ) . '"></span>'; 
  371. } elseif ( $the_product->is_type( 'external' ) ) { 
  372. echo '<span class="product-type tips external" data-tip="' . esc_attr__( 'External/Affiliate', 'woocommerce' ) . '"></span>'; 
  373. } elseif ( $the_product->is_type( 'simple' ) ) { 
  374.  
  375. if ( $the_product->is_virtual() ) { 
  376. echo '<span class="product-type tips virtual" data-tip="' . esc_attr__( 'Virtual', 'woocommerce' ) . '"></span>'; 
  377. } elseif ( $the_product->is_downloadable() ) { 
  378. echo '<span class="product-type tips downloadable" data-tip="' . esc_attr__( 'Downloadable', 'woocommerce' ) . '"></span>'; 
  379. } else { 
  380. echo '<span class="product-type tips simple" data-tip="' . esc_attr__( 'Simple', 'woocommerce' ) . '"></span>'; 
  381. } elseif ( $the_product->is_type( 'variable' ) ) { 
  382. echo '<span class="product-type tips variable" data-tip="' . esc_attr__( 'Variable', 'woocommerce' ) . '"></span>'; 
  383. } else { 
  384. // Assuming that we have other types in future 
  385. echo '<span class="product-type tips ' . esc_attr( sanitize_html_class( $the_product->get_type() ) ) . '" data-tip="' . esc_attr( ucfirst( $the_product->get_type() ) ) . '"></span>'; 
  386. break; 
  387. case 'price' : 
  388. echo $the_product->get_price_html() ? $the_product->get_price_html() : '<span class="na">–</span>'; 
  389. break; 
  390. case 'product_cat' : 
  391. case 'product_tag' : 
  392. if ( ! $terms = get_the_terms( $post->ID, $column ) ) { 
  393. echo '<span class="na">–</span>'; 
  394. } else { 
  395. $termlist = array(); 
  396. foreach ( $terms as $term ) { 
  397. $termlist[] = '<a href="' . admin_url( 'edit.php?' . $column . '=' . $term->slug . '&post_type=product' ) . ' ">' . $term->name . '</a>'; 
  398.  
  399. echo implode( ', ', $termlist ); 
  400. break; 
  401. case 'featured' : 
  402. $url = wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_feature_product&product_id=' . $post->ID ), 'woocommerce-feature-product' ); 
  403. echo '<a href="' . esc_url( $url ) . '" aria-label="' . __( 'Toggle featured', 'woocommerce' ) . '">'; 
  404. if ( $the_product->is_featured() ) { 
  405. echo '<span class="wc-featured tips" data-tip="' . esc_attr__( 'Yes', 'woocommerce' ) . '">' . __( 'Yes', 'woocommerce' ) . '</span>'; 
  406. } else { 
  407. echo '<span class="wc-featured not-featured tips" data-tip="' . esc_attr__( 'No', 'woocommerce' ) . '">' . __( 'No', 'woocommerce' ) . '</span>'; 
  408. echo '</a>'; 
  409. break; 
  410. case 'is_in_stock' : 
  411.  
  412. if ( $the_product->is_in_stock() ) { 
  413. $stock_html = '<mark class="instock">' . __( 'In stock', 'woocommerce' ) . '</mark>'; 
  414. } else { 
  415. $stock_html = '<mark class="outofstock">' . __( 'Out of stock', 'woocommerce' ) . '</mark>'; 
  416.  
  417. if ( $the_product->managing_stock() ) { 
  418. $stock_html .= ' (' . wc_stock_amount( $the_product->get_stock_quantity() ) . ')'; 
  419.  
  420. echo apply_filters( 'woocommerce_admin_stock_html', $stock_html, $the_product ); 
  421.  
  422. break; 
  423. default : 
  424. break; 
  425.  
  426. /** 
  427. * Output custom columns for coupons. 
  428. * 
  429. * @param string $column 
  430. */ 
  431. public function render_shop_coupon_columns( $column ) { 
  432. global $post, $the_coupon; 
  433.  
  434. if ( empty( $the_coupon ) || $the_coupon->get_id() !== $post->ID ) { 
  435. $the_coupon = new WC_Coupon( $post->ID ); 
  436.  
  437. switch ( $column ) { 
  438. case 'coupon_code' : 
  439. $edit_link = get_edit_post_link( $post->ID ); 
  440. $title = $the_coupon->get_code(); 
  441.  
  442. echo '<strong><a class="row-title" href="' . esc_url( $edit_link ) . '">' . esc_html( $title ) . '</a>'; 
  443. _post_states( $post ); 
  444. echo '</strong>'; 
  445. break; 
  446. case 'type' : 
  447. echo esc_html( wc_get_coupon_type( $the_coupon->get_discount_type() ) ); 
  448. break; 
  449. case 'amount' : 
  450. echo esc_html( wc_format_localized_price( $the_coupon->get_amount() ) ); 
  451. break; 
  452. case 'products' : 
  453. $product_ids = $the_coupon->get_product_ids(); 
  454.  
  455. if ( sizeof( $product_ids ) > 0 ) { 
  456. echo esc_html( implode( ', ', $product_ids ) ); 
  457. } else { 
  458. echo '–'; 
  459. break; 
  460. case 'usage_limit' : 
  461. $usage_limit = $the_coupon->get_usage_limit(); 
  462.  
  463. if ( $usage_limit ) { 
  464. echo esc_html( $usage_limit ); 
  465. } else { 
  466. echo '–'; 
  467. break; 
  468. case 'usage' : 
  469. $usage_count = $the_coupon->get_usage_count(); 
  470. $usage_limit = $the_coupon->get_usage_limit(); 
  471.  
  472. /** translators: 1: count 2: limit */ 
  473. printf( 
  474. __( '%1$s / %2$s', 'woocommerce' ),  
  475. esc_html( $usage_count ),  
  476. esc_html( $usage_limit ? $usage_limit : '∞' ) 
  477. ); 
  478. break; 
  479. case 'expiry_date' : 
  480. $expiry_date = $the_coupon->get_date_expires(); 
  481.  
  482. if ( $expiry_date ) { 
  483. echo esc_html( $expiry_date->date_i18n( 'F j, Y' ) ); 
  484. } else { 
  485. echo '–'; 
  486. break; 
  487. case 'description' : 
  488. echo wp_kses_post( $the_coupon->get_description() ? $the_coupon->get_description() : '–' ); 
  489. break; 
  490.  
  491. /** 
  492. * Output custom columns for coupons. 
  493. * @param string $column 
  494. */ 
  495. public function render_shop_order_columns( $column ) { 
  496. global $post, $the_order; 
  497.  
  498. if ( empty( $the_order ) || $the_order->get_id() !== $post->ID ) { 
  499. $the_order = wc_get_order( $post->ID ); 
  500.  
  501. switch ( $column ) { 
  502. case 'order_status' : 
  503. printf( '<mark class="%s tips" data-tip="%s">%s</mark>', esc_attr( sanitize_html_class( $the_order->get_status() ) ), esc_attr( wc_get_order_status_name( $the_order->get_status() ) ), esc_html( wc_get_order_status_name( $the_order->get_status() ) ) ); 
  504. break; 
  505. case 'order_date' : 
  506. printf( '<time datetime="%s">%s</time>', esc_attr( $the_order->get_date_created()->date( 'c' ) ), esc_html( $the_order->get_date_created()->date_i18n( apply_filters( 'woocommerce_admin_order_date_format', __( 'Y-m-d', 'woocommerce' ) ) ) ) ); 
  507. break; 
  508. case 'customer_message' : 
  509. if ( $the_order->get_customer_note() ) { 
  510. echo '<span class="note-on tips" data-tip="' . wc_sanitize_tooltip( $the_order->get_customer_note() ) . '">' . __( 'Yes', 'woocommerce' ) . '</span>'; 
  511. } else { 
  512. echo '<span class="na">–</span>'; 
  513.  
  514. break; 
  515. case 'billing_address' : 
  516.  
  517. if ( $address = $the_order->get_formatted_billing_address() ) { 
  518. echo esc_html( preg_replace( '#<br\s*/?>#i', ', ', $address ) ); 
  519. } else { 
  520. echo '–'; 
  521.  
  522. if ( $the_order->get_billing_phone() ) { 
  523. echo '<small class="meta">' . __( 'Phone:', 'woocommerce' ) . ' ' . esc_html( $the_order->get_billing_phone() ) . '</small>'; 
  524.  
  525. break; 
  526. case 'shipping_address' : 
  527.  
  528. if ( $address = $the_order->get_formatted_shipping_address() ) { 
  529. echo '<a target="_blank" href="' . esc_url( $the_order->get_shipping_address_map_url() ) . '">' . esc_html( preg_replace( '#<br\s*/?>#i', ', ', $address ) ) . '</a>'; 
  530. } else { 
  531. echo '–'; 
  532.  
  533. if ( $the_order->get_shipping_method() ) { 
  534. echo '<small class="meta">' . __( 'Via', 'woocommerce' ) . ' ' . esc_html( $the_order->get_shipping_method() ) . '</small>'; 
  535.  
  536. break; 
  537. case 'order_notes' : 
  538.  
  539. if ( $post->comment_count ) { 
  540.  
  541. // check the status of the post 
  542. $status = ( 'trash' !== $post->post_status ) ? '' : 'post-trashed'; 
  543.  
  544. $latest_notes = get_comments( array( 
  545. 'post_id' => $post->ID,  
  546. 'number' => 1,  
  547. 'status' => $status,  
  548. ) ); 
  549.  
  550. $latest_note = current( $latest_notes ); 
  551.  
  552. if ( isset( $latest_note->comment_content ) && 1 == $post->comment_count ) { 
  553. echo '<span class="note-on tips" data-tip="' . wc_sanitize_tooltip( $latest_note->comment_content ) . '">' . __( 'Yes', 'woocommerce' ) . '</span>'; 
  554. } elseif ( isset( $latest_note->comment_content ) ) { 
  555. /** translators: %d: notes count */ 
  556. echo '<span class="note-on tips" data-tip="' . wc_sanitize_tooltip( $latest_note->comment_content . '<br/><small style="display:block">' . sprintf( _n( 'plus %d other note', 'plus %d other notes', ( $post->comment_count - 1 ), 'woocommerce' ), $post->comment_count - 1 ) . '</small>' ) . '">' . __( 'Yes', 'woocommerce' ) . '</span>'; 
  557. } else { 
  558. /** translators: %d: notes count */ 
  559. echo '<span class="note-on tips" data-tip="' . wc_sanitize_tooltip( sprintf( _n( '%d note', '%d notes', $post->comment_count, 'woocommerce' ), $post->comment_count ) ) . '">' . __( 'Yes', 'woocommerce' ) . '</span>'; 
  560. } else { 
  561. echo '<span class="na">–</span>'; 
  562.  
  563. break; 
  564. case 'order_total' : 
  565. echo $the_order->get_formatted_order_total(); 
  566.  
  567. if ( $the_order->get_payment_method_title() ) { 
  568. echo '<small class="meta">' . __( 'Via', 'woocommerce' ) . ' ' . esc_html( $the_order->get_payment_method_title() ) . '</small>'; 
  569. break; 
  570. case 'order_title' : 
  571.  
  572. if ( $the_order->get_customer_id() ) { 
  573. $user = get_user_by( 'id', $the_order->get_customer_id() ); 
  574. $username = '<a href="user-edit.php?user_id=' . absint( $the_order->get_customer_id() ) . '">'; 
  575. $username .= esc_html( ucwords( $user->display_name ) ); 
  576. $username .= '</a>'; 
  577. } elseif ( $the_order->get_billing_first_name() || $the_order->get_billing_last_name() ) { 
  578. /** translators: 1: first name 2: last name */ 
  579. $username = trim( sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $the_order->get_billing_first_name(), $the_order->get_billing_last_name() ) ); 
  580. } elseif ( $the_order->get_billing_company() ) { 
  581. $username = trim( $the_order->get_billing_company() ); 
  582. } else { 
  583. $username = __( 'Guest', 'woocommerce' ); 
  584.  
  585. /** translators: 1: order and number (i.e. Order #13) 2: user name */ 
  586. printf( 
  587. __( '%1$s by %2$s', 'woocommerce' ),  
  588. '<a href="' . admin_url( 'post.php?post=' . absint( $post->ID ) . '&action=edit' ) . '" class="row-title"><strong>#' . esc_attr( $the_order->get_order_number() ) . '</strong></a>',  
  589. $username 
  590. ); 
  591.  
  592. if ( $the_order->get_billing_email() ) { 
  593. echo '<small class="meta email"><a href="' . esc_url( 'mailto:' . $the_order->get_billing_email() ) . '">' . esc_html( $the_order->get_billing_email() ) . '</a></small>'; 
  594.  
  595. echo '<button type="button" class="toggle-row"><span class="screen-reader-text">' . __( 'Show more details', 'woocommerce' ) . '</span></button>'; 
  596.  
  597. break; 
  598. case 'order_actions' : 
  599.  
  600. ?><p> 
  601. <?php 
  602. do_action( 'woocommerce_admin_order_actions_start', $the_order ); 
  603.  
  604. $actions = array(); 
  605.  
  606. if ( $the_order->has_status( array( 'pending', 'on-hold' ) ) ) { 
  607. $actions['processing'] = array( 
  608. 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_mark_order_status&status=processing&order_id=' . $post->ID ), 'woocommerce-mark-order-status' ),  
  609. 'name' => __( 'Processing', 'woocommerce' ),  
  610. 'action' => "processing",  
  611. ); 
  612.  
  613. if ( $the_order->has_status( array( 'pending', 'on-hold', 'processing' ) ) ) { 
  614. $actions['complete'] = array( 
  615. 'url' => wp_nonce_url( admin_url( 'admin-ajax.php?action=woocommerce_mark_order_status&status=completed&order_id=' . $post->ID ), 'woocommerce-mark-order-status' ),  
  616. 'name' => __( 'Complete', 'woocommerce' ),  
  617. 'action' => "complete",  
  618. ); 
  619.  
  620. $actions['view'] = array( 
  621. 'url' => admin_url( 'post.php?post=' . $post->ID . '&action=edit' ),  
  622. 'name' => __( 'View', 'woocommerce' ),  
  623. 'action' => "view",  
  624. ); 
  625.  
  626. $actions = apply_filters( 'woocommerce_admin_order_actions', $actions, $the_order ); 
  627.  
  628. foreach ( $actions as $action ) { 
  629. printf( '<a class="button tips %s" href="%s" data-tip="%s">%s</a>', esc_attr( $action['action'] ), esc_url( $action['url'] ), esc_attr( $action['name'] ), esc_attr( $action['name'] ) ); 
  630.  
  631. do_action( 'woocommerce_admin_order_actions_end', $the_order ); 
  632. ?> 
  633. </p><?php 
  634.  
  635. break; 
  636.  
  637. /** 
  638. * Make columns sortable - https://gist.github.com/906872. 
  639. * 
  640. * @param array $columns 
  641. * @return array 
  642. */ 
  643. public function product_sortable_columns( $columns ) { 
  644. $custom = array( 
  645. 'price' => 'price',  
  646. 'sku' => 'sku',  
  647. 'name' => 'title',  
  648. ); 
  649. return wp_parse_args( $custom, $columns ); 
  650.  
  651. /** 
  652. * Make columns sortable - https://gist.github.com/906872. 
  653. * 
  654. * @param array $columns 
  655. * @return array 
  656. */ 
  657. public function shop_coupon_sortable_columns( $columns ) { 
  658. return $columns; 
  659.  
  660. /** 
  661. * Make columns sortable - https://gist.github.com/906872. 
  662. * 
  663. * @param array $columns 
  664. * @return array 
  665. */ 
  666. public function shop_order_sortable_columns( $columns ) { 
  667. $custom = array( 
  668. 'order_title' => 'ID',  
  669. 'order_total' => 'order_total',  
  670. 'order_date' => 'date',  
  671. ); 
  672. unset( $columns['comments'] ); 
  673.  
  674. return wp_parse_args( $custom, $columns ); 
  675.  
  676. /** 
  677. * Set list table primary column for products and orders. 
  678. * Support for WordPress 4.3. 
  679. * 
  680. * @param string $default 
  681. * @param string $screen_id 
  682. * 
  683. * @return string 
  684. */ 
  685. public function list_table_primary_column( $default, $screen_id ) { 
  686.  
  687. if ( 'edit-product' === $screen_id ) { 
  688. return 'name'; 
  689.  
  690. if ( 'edit-shop_order' === $screen_id ) { 
  691. return 'order_title'; 
  692.  
  693. if ( 'edit-shop_coupon' === $screen_id ) { 
  694. return 'coupon_code'; 
  695.  
  696. return $default; 
  697.  
  698. /** 
  699. * Set row actions for products and orders. 
  700. * 
  701. * @param array $actions 
  702. * @param WP_Post $post 
  703. * 
  704. * @return array 
  705. */ 
  706. public function row_actions( $actions, $post ) { 
  707. if ( 'product' === $post->post_type ) { 
  708. return array_merge( array( 'id' => 'ID: ' . $post->ID ), $actions ); 
  709.  
  710. if ( in_array( $post->post_type, array( 'shop_order', 'shop_coupon' ) ) ) { 
  711. if ( isset( $actions['inline hide-if-no-js'] ) ) { 
  712. unset( $actions['inline hide-if-no-js'] ); 
  713.  
  714. return $actions; 
  715.  
  716. /** 
  717. * Product sorting link. 
  718. * 
  719. * Based on Simple Page Ordering by 10up (https://wordpress.org/plugins/simple-page-ordering/). 
  720. * 
  721. * @param array $views 
  722. * @return array 
  723. */ 
  724. public function product_sorting_link( $views ) { 
  725. global $post_type, $wp_query; 
  726.  
  727. if ( ! current_user_can( 'edit_others_pages' ) ) { 
  728. return $views; 
  729.  
  730. $class = ( isset( $wp_query->query['orderby'] ) && 'menu_order title' === $wp_query->query['orderby'] ) ? 'current' : ''; 
  731. $query_string = remove_query_arg( array( 'orderby', 'order' ) ); 
  732. $query_string = add_query_arg( 'orderby', urlencode( 'menu_order title' ), $query_string ); 
  733. $query_string = add_query_arg( 'order', urlencode( 'ASC' ), $query_string ); 
  734. $views['byorder'] = '<a href="' . esc_url( $query_string ) . '" class="' . esc_attr( $class ) . '">' . __( 'Sort products', 'woocommerce' ) . '</a>'; 
  735.  
  736. return $views; 
  737.  
  738. /** 
  739. * Custom bulk edit - form. 
  740. * 
  741. * @param mixed $column_name 
  742. * @param mixed $post_type 
  743. */ 
  744. public function bulk_edit( $column_name, $post_type ) { 
  745.  
  746. if ( 'price' != $column_name || 'product' != $post_type ) { 
  747. return; 
  748.  
  749. $shipping_class = get_terms( 'product_shipping_class', array( 
  750. 'hide_empty' => false,  
  751. ) ); 
  752.  
  753. include( WC()->plugin_path() . '/includes/admin/views/html-bulk-edit-product.php' ); 
  754.  
  755. /** 
  756. * Custom quick edit - form. 
  757. * 
  758. * @param mixed $column_name 
  759. * @param mixed $post_type 
  760. */ 
  761. public function quick_edit( $column_name, $post_type ) { 
  762.  
  763. if ( 'price' != $column_name || 'product' != $post_type ) { 
  764. return; 
  765.  
  766. $shipping_class = get_terms( 'product_shipping_class', array( 
  767. 'hide_empty' => false,  
  768. ) ); 
  769.  
  770. include( WC()->plugin_path() . '/includes/admin/views/html-quick-edit-product.php' ); 
  771.  
  772. /** 
  773. * Offers a way to hook into save post without causing an infinite loop 
  774. * when quick/bulk saving product info. 
  775. * 
  776. * @since 3.0.0 
  777. * @param int $post_id 
  778. * @param object $post 
  779. */ 
  780. public function bulk_and_quick_edit_hook( $post_id, $post ) { 
  781. remove_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ) ); 
  782. do_action( 'woocommerce_product_bulk_and_quick_edit', $post_id, $post ); 
  783. add_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ), 10, 2 ); 
  784.  
  785. /** 
  786. * Quick and bulk edit saving. 
  787. * 
  788. * @param int $post_id 
  789. * @param WP_Post $post 
  790. * @return int 
  791. */ 
  792. public function bulk_and_quick_edit_save_post( $post_id, $post ) { 
  793. // If this is an autosave, our form has not been submitted, so we don't want to do anything. 
  794. if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) { 
  795. return $post_id; 
  796.  
  797. // Don't save revisions and autosaves 
  798. if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) ) { 
  799. return $post_id; 
  800.  
  801. // Check post type is product 
  802. if ( 'product' != $post->post_type ) { 
  803. return $post_id; 
  804.  
  805. // Check user permission 
  806. if ( ! current_user_can( 'edit_post', $post_id ) ) { 
  807. return $post_id; 
  808.  
  809. // Check nonces 
  810. if ( ! isset( $_REQUEST['woocommerce_quick_edit_nonce'] ) && ! isset( $_REQUEST['woocommerce_bulk_edit_nonce'] ) ) { 
  811. return $post_id; 
  812. if ( isset( $_REQUEST['woocommerce_quick_edit_nonce'] ) && ! wp_verify_nonce( $_REQUEST['woocommerce_quick_edit_nonce'], 'woocommerce_quick_edit_nonce' ) ) { 
  813. return $post_id; 
  814. if ( isset( $_REQUEST['woocommerce_bulk_edit_nonce'] ) && ! wp_verify_nonce( $_REQUEST['woocommerce_bulk_edit_nonce'], 'woocommerce_bulk_edit_nonce' ) ) { 
  815. return $post_id; 
  816.  
  817. // Get the product and save 
  818. $product = wc_get_product( $post ); 
  819.  
  820. if ( ! empty( $_REQUEST['woocommerce_quick_edit'] ) ) { 
  821. $this->quick_edit_save( $post_id, $product ); 
  822. } else { 
  823. $this->bulk_edit_save( $post_id, $product ); 
  824.  
  825. return $post_id; 
  826.  
  827. /** 
  828. * Quick edit. 
  829. * 
  830. * @param integer $post_id 
  831. * @param WC_Product $product 
  832. */ 
  833. private function quick_edit_save( $post_id, $product ) { 
  834. $data_store = $product->get_data_store(); 
  835. $old_regular_price = $product->get_regular_price(); 
  836. $old_sale_price = $product->get_sale_price(); 
  837.  
  838. // Save fields 
  839. if ( isset( $_REQUEST['_sku'] ) ) { 
  840. $sku = $product->get_sku(); 
  841. $new_sku = (string) wc_clean( $_REQUEST['_sku'] ); 
  842.  
  843. if ( $new_sku !== $sku ) { 
  844. if ( ! empty( $new_sku ) ) { 
  845. $unique_sku = wc_product_has_unique_sku( $post_id, $new_sku ); 
  846. if ( $unique_sku ) { 
  847. $product->set_sku( $new_sku ); 
  848. } else { 
  849. $product->set_sku( '' ); 
  850.  
  851. if ( isset( $_REQUEST['_weight'] ) ) { 
  852. $product->set_weight( wc_clean( $_REQUEST['_weight'] ) ); 
  853.  
  854. if ( isset( $_REQUEST['_length'] ) ) { 
  855. $product->set_length( wc_clean( $_REQUEST['_length'] ) ); 
  856.  
  857. if ( isset( $_REQUEST['_width'] ) ) { 
  858. $product->set_width( wc_clean( $_REQUEST['_width'] ) ); 
  859.  
  860. if ( isset( $_REQUEST['_height'] ) ) { 
  861. $product->set_height( wc_clean( $_REQUEST['_height'] ) ); 
  862.  
  863. if ( ! empty( $_REQUEST['_shipping_class'] ) ) { 
  864. if ( '_no_shipping_class' === $_REQUEST['_shipping_class'] ) { 
  865. $product->set_shipping_class_id( 0 ); 
  866. } else { 
  867. $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $_REQUEST['_shipping_class'] ) ); 
  868. $product->set_shipping_class_id( $shipping_class_id ); 
  869.  
  870. if ( isset( $_REQUEST['_visibility'] ) ) { 
  871. $product->set_catalog_visibility( wc_clean( $_REQUEST['_visibility'] ) ); 
  872.  
  873. if ( isset( $_REQUEST['_featured'] ) ) { 
  874. $product->set_featured( true ); 
  875. } else { 
  876. $product->set_featured( false ); 
  877.  
  878. if ( isset( $_REQUEST['_tax_status'] ) ) { 
  879. $product->set_tax_status( wc_clean( $_REQUEST['_tax_status'] ) ); 
  880.  
  881. if ( isset( $_REQUEST['_tax_class'] ) ) { 
  882. $product->set_tax_class( wc_clean( $_REQUEST['_tax_class'] ) ); 
  883.  
  884. if ( $product->is_type( 'simple' ) || $product->is_type( 'external' ) ) { 
  885.  
  886. if ( isset( $_REQUEST['_regular_price'] ) ) { 
  887. $new_regular_price = ( '' === $_REQUEST['_regular_price'] ) ? '' : wc_format_decimal( $_REQUEST['_regular_price'] ); 
  888. $product->set_regular_price( $new_regular_price ); 
  889. } else { 
  890. $new_regular_price = null; 
  891. if ( isset( $_REQUEST['_sale_price'] ) ) { 
  892. $new_sale_price = ( '' === $_REQUEST['_sale_price'] ) ? '' : wc_format_decimal( $_REQUEST['_sale_price'] ); 
  893. $product->set_sale_price( $new_sale_price ); 
  894. } else { 
  895. $new_sale_price = null; 
  896.  
  897. // Handle price - remove dates and set to lowest 
  898. $price_changed = false; 
  899.  
  900. if ( ! is_null( $new_regular_price ) && $new_regular_price != $old_regular_price ) { 
  901. $price_changed = true; 
  902. } elseif ( ! is_null( $new_sale_price ) && $new_sale_price != $old_sale_price ) { 
  903. $price_changed = true; 
  904.  
  905. if ( $price_changed ) { 
  906. $product->set_date_on_sale_to( '' ); 
  907. $product->set_date_on_sale_from( '' ); 
  908.  
  909. // Handle Stock Data 
  910. $manage_stock = ! empty( $_REQUEST['_manage_stock'] ) && 'grouped' !== $product->get_type() ? 'yes' : 'no'; 
  911. $backorders = ! empty( $_REQUEST['_backorders'] ) ? wc_clean( $_REQUEST['_backorders'] ) : 'no'; 
  912. $stock_status = ! empty( $_REQUEST['_stock_status'] ) ? wc_clean( $_REQUEST['_stock_status'] ) : 'instock'; 
  913. $stock_amount = 'yes' === $manage_stock ? wc_stock_amount( $_REQUEST['_stock'] ) : ''; 
  914.  
  915. if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { 
  916.  
  917. // Apply product type constraints to stock status 
  918. if ( $product->is_type( 'external' ) ) { 
  919. // External always in stock 
  920. $stock_status = 'instock'; 
  921. } elseif ( $product->is_type( 'variable' ) ) { 
  922. // Stock status is always determined by children 
  923. foreach ( $product->get_children() as $child_id ) { 
  924. $child = wc_get_product( $child_id ); 
  925. if ( ! $product->get_manage_stock() ) { 
  926. $child->set_stock_status( $stock_status ); 
  927. $child->save(); 
  928.  
  929. $product = WC_Product_Variable::sync( $product, false ); 
  930.  
  931. $product->set_manage_stock( $manage_stock ); 
  932. $product->set_backorders( $backorders ); 
  933. $product->save(); 
  934.  
  935. if ( ! $product->is_type( 'variable' ) ) { 
  936. wc_update_product_stock_status( $post_id, $stock_status ); 
  937.  
  938. wc_update_product_stock( $post_id, $stock_amount ); 
  939.  
  940. } else { 
  941. $product->save(); 
  942. wc_update_product_stock_status( $post_id, $stock_status ); 
  943.  
  944. do_action( 'woocommerce_product_quick_edit_save', $product ); 
  945.  
  946. /** 
  947. * Bulk edit. 
  948. * 
  949. * @param integer $post_id 
  950. * @param WC_Product $product 
  951. */ 
  952. public function bulk_edit_save( $post_id, $product ) { 
  953. $data_store = $product->get_data_store(); 
  954. $old_regular_price = $product->get_regular_price(); 
  955. $old_sale_price = $product->get_sale_price(); 
  956.  
  957. // Save fields 
  958. if ( ! empty( $_REQUEST['change_weight'] ) && isset( $_REQUEST['_weight'] ) ) { 
  959. $product->set_weight( wc_clean( stripslashes( $_REQUEST['_weight'] ) ) ); 
  960.  
  961. if ( ! empty( $_REQUEST['change_dimensions'] ) ) { 
  962. if ( isset( $_REQUEST['_length'] ) ) { 
  963. $product->set_length( wc_clean( stripslashes( $_REQUEST['_length'] ) ) ); 
  964. if ( isset( $_REQUEST['_width'] ) ) { 
  965. $product->set_width( wc_clean( stripslashes( $_REQUEST['_width'] ) ) ); 
  966. if ( isset( $_REQUEST['_height'] ) ) { 
  967. $product->set_height( wc_clean( stripslashes( $_REQUEST['_height'] ) ) ); 
  968.  
  969. if ( ! empty( $_REQUEST['_tax_status'] ) ) { 
  970. $product->set_tax_status( wc_clean( $_REQUEST['_tax_status'] ) ); 
  971.  
  972. if ( ! empty( $_REQUEST['_tax_class'] ) ) { 
  973. $tax_class = wc_clean( $_REQUEST['_tax_class'] ); 
  974. if ( 'standard' == $tax_class ) { 
  975. $tax_class = ''; 
  976. $product->set_tax_class( $tax_class ); 
  977.  
  978. if ( ! empty( $_REQUEST['_shipping_class'] ) ) { 
  979. if ( '_no_shipping_class' === $_REQUEST['_shipping_class'] ) { 
  980. $product->set_shipping_class_id( 0 ); 
  981. } else { 
  982. $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $_REQUEST['_shipping_class'] ) ); 
  983. $product->set_shipping_class_id( $shipping_class_id ); 
  984.  
  985. if ( ! empty( $_REQUEST['_visibility'] ) ) { 
  986. $product->set_catalog_visibility( wc_clean( $_REQUEST['_visibility'] ) ); 
  987.  
  988. if ( ! empty( $_REQUEST['_featured'] ) ) { 
  989. $product->set_featured( stripslashes( $_REQUEST['_featured'] ) ); 
  990.  
  991. // Sold Individually 
  992. if ( ! empty( $_REQUEST['_sold_individually'] ) ) { 
  993. if ( 'yes' === $_REQUEST['_sold_individually'] ) { 
  994. $product->set_sold_individually( 'yes' ); 
  995. } else { 
  996. $product->set_sold_individually( '' ); 
  997.  
  998. // Handle price - remove dates and set to lowest 
  999. $change_price_product_types = apply_filters( 'woocommerce_bulk_edit_save_price_product_types', array( 'simple', 'external' ) ); 
  1000. $can_product_type_change_price = false; 
  1001. foreach ( $change_price_product_types as $product_type ) { 
  1002. if ( $product->is_type( $product_type ) ) { 
  1003. $can_product_type_change_price = true; 
  1004. break; 
  1005.  
  1006. if ( $can_product_type_change_price ) { 
  1007.  
  1008. $price_changed = false; 
  1009.  
  1010. if ( ! empty( $_REQUEST['change_regular_price'] ) ) { 
  1011. $change_regular_price = absint( $_REQUEST['change_regular_price'] ); 
  1012. $regular_price = esc_attr( stripslashes( $_REQUEST['_regular_price'] ) ); 
  1013.  
  1014. switch ( $change_regular_price ) { 
  1015. case 1 : 
  1016. $new_price = $regular_price; 
  1017. break; 
  1018. case 2 : 
  1019. if ( strstr( $regular_price, '%' ) ) { 
  1020. $percent = str_replace( '%', '', $regular_price ) / 100; 
  1021. $new_price = $old_regular_price + ( round( $old_regular_price * $percent, wc_get_price_decimals() ) ); 
  1022. } else { 
  1023. $new_price = $old_regular_price + $regular_price; 
  1024. break; 
  1025. case 3 : 
  1026. if ( strstr( $regular_price, '%' ) ) { 
  1027. $percent = str_replace( '%', '', $regular_price ) / 100; 
  1028. $new_price = max( 0, $old_regular_price - ( round( $old_regular_price * $percent, wc_get_price_decimals() ) ) ); 
  1029. } else { 
  1030. $new_price = max( 0, $old_regular_price - $regular_price ); 
  1031. break; 
  1032.  
  1033. default : 
  1034. break; 
  1035.  
  1036. if ( isset( $new_price ) && $new_price != $old_regular_price ) { 
  1037. $price_changed = true; 
  1038. $new_price = round( $new_price, wc_get_price_decimals() ); 
  1039. $product->set_regular_price( $new_price ); 
  1040.  
  1041. if ( ! empty( $_REQUEST['change_sale_price'] ) ) { 
  1042. $change_sale_price = absint( $_REQUEST['change_sale_price'] ); 
  1043. $sale_price = esc_attr( stripslashes( $_REQUEST['_sale_price'] ) ); 
  1044.  
  1045. switch ( $change_sale_price ) { 
  1046. case 1 : 
  1047. $new_price = $sale_price; 
  1048. break; 
  1049. case 2 : 
  1050. if ( strstr( $sale_price, '%' ) ) { 
  1051. $percent = str_replace( '%', '', $sale_price ) / 100; 
  1052. $new_price = $old_sale_price + ( $old_sale_price * $percent ); 
  1053. } else { 
  1054. $new_price = $old_sale_price + $sale_price; 
  1055. break; 
  1056. case 3 : 
  1057. if ( strstr( $sale_price, '%' ) ) { 
  1058. $percent = str_replace( '%', '', $sale_price ) / 100; 
  1059. $new_price = max( 0, $old_sale_price - ( $old_sale_price * $percent ) ); 
  1060. } else { 
  1061. $new_price = max( 0, $old_sale_price - $sale_price ); 
  1062. break; 
  1063. case 4 : 
  1064. if ( strstr( $sale_price, '%' ) ) { 
  1065. $percent = str_replace( '%', '', $sale_price ) / 100; 
  1066. $new_price = max( 0, $product->regular_price - ( $product->regular_price * $percent ) ); 
  1067. } else { 
  1068. $new_price = max( 0, $product->regular_price - $sale_price ); 
  1069. break; 
  1070.  
  1071. default : 
  1072. break; 
  1073.  
  1074. if ( isset( $new_price ) && $new_price != $old_sale_price ) { 
  1075. $price_changed = true; 
  1076. $new_price = ! empty( $new_price ) || '0' === $new_price ? round( $new_price, wc_get_price_decimals() ) : ''; 
  1077. $product->set_sale_price( $new_price ); 
  1078.  
  1079. if ( $price_changed ) { 
  1080. $product->set_date_on_sale_to( '' ); 
  1081. $product->set_date_on_sale_from( '' ); 
  1082.  
  1083. if ( $product->get_regular_price() < $product->get_sale_price() ) { 
  1084. $product->set_sale_price( '' ); 
  1085.  
  1086. // Handle Stock Data 
  1087. $was_managing_stock = $product->get_manage_stock() ? 'yes' : 'no'; 
  1088. $stock_status = $product->get_stock_status(); 
  1089. $backorders = $product->get_backorders(); 
  1090.  
  1091. $backorders = ! empty( $_REQUEST['_backorders'] ) ? wc_clean( $_REQUEST['_backorders'] ) : $backorders; 
  1092. $stock_status = ! empty( $_REQUEST['_stock_status'] ) ? wc_clean( $_REQUEST['_stock_status'] ) : $stock_status; 
  1093.  
  1094. if ( ! empty( $_REQUEST['_manage_stock'] ) ) { 
  1095. $manage_stock = 'yes' === wc_clean( $_REQUEST['_manage_stock'] ) && 'grouped' !== $product->product_type ? 'yes' : 'no'; 
  1096. } else { 
  1097. $manage_stock = $was_managing_stock; 
  1098.  
  1099. $stock_amount = 'yes' === $manage_stock && ! empty( $_REQUEST['change_stock'] ) ? wc_stock_amount( $_REQUEST['_stock'] ) : $product->get_stock_quantity(); 
  1100.  
  1101. if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) { 
  1102.  
  1103. // Apply product type constraints to stock status 
  1104. if ( $product->is_type( 'external' ) ) { 
  1105. // External always in stock 
  1106. $stock_status = 'instock'; 
  1107. } elseif ( $product->is_type( 'variable' ) ) { 
  1108. // Stock status is always determined by children 
  1109. foreach ( $product->get_children() as $child_id ) { 
  1110. $child = wc_get_product( $child_id ); 
  1111. if ( ! $product->get_manage_stock() ) { 
  1112. $child->set_stock_status( $stock_status ); 
  1113. $child->save(); 
  1114.  
  1115. $product = WC_Product_Variable::sync( $product, false ); 
  1116.  
  1117. $product->set_manage_stock( $manage_stock ); 
  1118. $product->set_backorders( $backorders ); 
  1119. $product->save(); 
  1120.  
  1121. if ( ! $product->is_type( 'variable' ) ) { 
  1122. wc_update_product_stock_status( $post_id, $stock_status ); 
  1123.  
  1124. wc_update_product_stock( $post_id, $stock_amount ); 
  1125.  
  1126. } else { 
  1127. $product->save(); 
  1128. wc_update_product_stock_status( $post_id, $stock_status ); 
  1129.  
  1130. do_action( 'woocommerce_product_bulk_edit_save', $product ); 
  1131.  
  1132. /** 
  1133. * Manipulate shop order bulk actions. 
  1134. * 
  1135. * @param array $actions List of actions. 
  1136. * @return array 
  1137. */ 
  1138. public function shop_order_bulk_actions( $actions ) { 
  1139. if ( isset( $actions['edit'] ) ) { 
  1140. unset( $actions['edit'] ); 
  1141.  
  1142. $actions['mark_processing'] = __( 'Mark processing', 'woocommerce' ); 
  1143. $actions['mark_on-hold'] = __( 'Mark on-hold', 'woocommerce' ); 
  1144. $actions['mark_completed'] = __( 'Mark complete', 'woocommerce' ); 
  1145.  
  1146. return $actions; 
  1147.  
  1148. /** 
  1149. * Handle shop order bulk actions. 
  1150. * 
  1151. * @since 3.0.0 
  1152. * @param string $redirect_to URL to redirect to. 
  1153. * @param string $action Action name. 
  1154. * @param array $ids List of ids. 
  1155. * @return string 
  1156. */ 
  1157. public function handle_shop_order_bulk_actions( $redirect_to, $action, $ids ) { 
  1158. // Bail out if this is not a status-changing action. 
  1159. if ( false === strpos( $action, 'mark_' ) ) { 
  1160. return $redirect_to; 
  1161.  
  1162. $order_statuses = wc_get_order_statuses(); 
  1163. $new_status = substr( $action, 5 ); // Get the status name from action. 
  1164. $report_action = 'marked_' . $new_status; 
  1165.  
  1166. // Sanity check: bail out if this is actually not a status, or is 
  1167. // not a registered status. 
  1168. if ( ! isset( $order_statuses[ 'wc-' . $new_status ] ) ) { 
  1169. return $redirect_to; 
  1170.  
  1171. $changed = 0; 
  1172. $ids = array_map( 'absint', $ids ); 
  1173.  
  1174. foreach ( $ids as $id ) { 
  1175. $order = wc_get_order( $id ); 
  1176. $order->update_status( $new_status, __( 'Order status changed by bulk edit:', 'woocommerce' ), true ); 
  1177. do_action( 'woocommerce_order_edit_status', $id, $new_status ); 
  1178. $changed++; 
  1179.  
  1180. $redirect_to = add_query_arg( array( 
  1181. 'post_type' => 'shop_order',  
  1182. $report_action => true,  
  1183. 'changed' => $changed,  
  1184. 'ids' => join( ', ', $ids ),  
  1185. ), $redirect_to ); 
  1186.  
  1187. return esc_url_raw( $redirect_to ); 
  1188.  
  1189. /** 
  1190. * Show confirmation message that order status changed for number of orders. 
  1191. */ 
  1192. public function bulk_admin_notices() { 
  1193. global $post_type, $pagenow; 
  1194.  
  1195. // Bail out if not on shop order list page 
  1196. if ( 'edit.php' !== $pagenow || 'shop_order' !== $post_type ) { 
  1197. return; 
  1198.  
  1199. $order_statuses = wc_get_order_statuses(); 
  1200.  
  1201. // Check if any status changes happened 
  1202. foreach ( $order_statuses as $slug => $name ) { 
  1203.  
  1204. if ( isset( $_REQUEST[ 'marked_' . str_replace( 'wc-', '', $slug ) ] ) ) { 
  1205.  
  1206. $number = isset( $_REQUEST['changed'] ) ? absint( $_REQUEST['changed'] ) : 0; 
  1207. /** translators: %s: orders count */ 
  1208. $message = sprintf( _n( 'Order status changed.', '%s order statuses changed.', $number, 'woocommerce' ), number_format_i18n( $number ) ); 
  1209. echo '<div class="updated"><p>' . $message . '</p></div>'; 
  1210.  
  1211. break; 
  1212.  
  1213. /** 
  1214. * Search custom fields as well as content. 
  1215. * @param WP_Query $wp 
  1216. */ 
  1217. public function shop_order_search_custom_fields( $wp ) { 
  1218. global $pagenow; 
  1219.  
  1220. if ( 'edit.php' != $pagenow || empty( $wp->query_vars['s'] ) || 'shop_order' !== $wp->query_vars['post_type'] ) { 
  1221. return; 
  1222.  
  1223. $post_ids = wc_order_search( $_GET['s'] ); 
  1224.  
  1225. if ( ! empty( $post_ids ) ) { 
  1226. // Remove "s" - we don't want to search order name. 
  1227. unset( $wp->query_vars['s'] ); 
  1228.  
  1229. // so we know we're doing this. 
  1230. $wp->query_vars['shop_order_search'] = true; 
  1231.  
  1232. // Search by found posts. 
  1233. $wp->query_vars['post__in'] = array_merge( $post_ids, array( 0 ) ); 
  1234.  
  1235. /** 
  1236. * Change the label when searching orders. 
  1237. * 
  1238. * @param mixed $query 
  1239. * @return string 
  1240. */ 
  1241. public function shop_order_search_label( $query ) { 
  1242. global $pagenow, $typenow; 
  1243.  
  1244. if ( 'edit.php' != $pagenow ) { 
  1245. return $query; 
  1246.  
  1247. if ( 'shop_order' !== $typenow ) { 
  1248. return $query; 
  1249.  
  1250. if ( ! get_query_var( 'shop_order_search' ) ) { 
  1251. return $query; 
  1252.  
  1253. return wp_unslash( $_GET['s'] ); 
  1254.  
  1255. /** 
  1256. * Query vars for custom searches. 
  1257. * 
  1258. * @param mixed $public_query_vars 
  1259. * @return array 
  1260. */ 
  1261. public function add_custom_query_var( $public_query_vars ) { 
  1262. $public_query_vars[] = 'sku'; 
  1263. $public_query_vars[] = 'shop_order_search'; 
  1264.  
  1265. return $public_query_vars; 
  1266.  
  1267. /** 
  1268. * Filters for post types. 
  1269. */ 
  1270. public function restrict_manage_posts() { 
  1271. global $typenow; 
  1272.  
  1273. if ( in_array( $typenow, wc_get_order_types( 'order-meta-boxes' ) ) ) { 
  1274. $this->shop_order_filters(); 
  1275. } elseif ( 'product' == $typenow ) { 
  1276. $this->product_filters(); 
  1277. } elseif ( 'shop_coupon' == $typenow ) { 
  1278. $this->shop_coupon_filters(); 
  1279.  
  1280. /** 
  1281. * Show a category filter box. 
  1282. */ 
  1283. public function product_filters() { 
  1284. global $wp_query; 
  1285.  
  1286. // Category Filtering 
  1287. wc_product_dropdown_categories(); 
  1288.  
  1289. // Type filtering 
  1290. $terms = get_terms( 'product_type' ); 
  1291. $output = '<select name="product_type" id="dropdown_product_type">'; 
  1292. $output .= '<option value="">' . __( 'Show all product types', 'woocommerce' ) . '</option>'; 
  1293.  
  1294. foreach ( $terms as $term ) { 
  1295. $output .= '<option value="' . sanitize_title( $term->name ) . '" '; 
  1296.  
  1297. if ( isset( $wp_query->query['product_type'] ) ) { 
  1298. $output .= selected( $term->slug, $wp_query->query['product_type'], false ); 
  1299.  
  1300. $output .= '>'; 
  1301.  
  1302. switch ( $term->name ) { 
  1303. case 'grouped' : 
  1304. $output .= __( 'Grouped product', 'woocommerce' ); 
  1305. break; 
  1306. case 'external' : 
  1307. $output .= __( 'External/Affiliate product', 'woocommerce' ); 
  1308. break; 
  1309. case 'variable' : 
  1310. $output .= __( 'Variable product', 'woocommerce' ); 
  1311. break; 
  1312. case 'simple' : 
  1313. $output .= __( 'Simple product', 'woocommerce' ); 
  1314. break; 
  1315. default : 
  1316. // Assuming that we have other types in future 
  1317. $output .= ucfirst( $term->name ); 
  1318. break; 
  1319.  
  1320. $output .= '</option>'; 
  1321.  
  1322. if ( 'simple' == $term->name ) { 
  1323.  
  1324. $output .= '<option value="downloadable" '; 
  1325.  
  1326. if ( isset( $wp_query->query['product_type'] ) ) { 
  1327. $output .= selected( 'downloadable', $wp_query->query['product_type'], false ); 
  1328.  
  1329. $output .= '> ' . ( is_rtl() ? '←' : '→' ) . ' ' . __( 'Downloadable', 'woocommerce' ) . '</option>'; 
  1330.  
  1331. $output .= '<option value="virtual" '; 
  1332.  
  1333. if ( isset( $wp_query->query['product_type'] ) ) { 
  1334. $output .= selected( 'virtual', $wp_query->query['product_type'], false ); 
  1335.  
  1336. $output .= '> ' . ( is_rtl() ? '←' : '→' ) . ' ' . __( 'Virtual', 'woocommerce' ) . '</option>'; 
  1337.  
  1338. $output .= '</select>'; 
  1339.  
  1340. echo apply_filters( 'woocommerce_product_filters', $output ); 
  1341.  
  1342. /** 
  1343. * Show custom filters to filter coupons by type. 
  1344. */ 
  1345. public function shop_coupon_filters() { 
  1346. ?> 
  1347. <select name="coupon_type" id="dropdown_shop_coupon_type"> 
  1348. <option value=""><?php _e( 'Show all types', 'woocommerce' ); ?></option> 
  1349. <?php 
  1350. $types = wc_get_coupon_types(); 
  1351.  
  1352. foreach ( $types as $name => $type ) { 
  1353. echo '<option value="' . esc_attr( $name ) . '"'; 
  1354.  
  1355. if ( isset( $_GET['coupon_type'] ) ) { 
  1356. selected( $name, $_GET['coupon_type'] ); 
  1357.  
  1358. echo '>' . esc_html( $type ) . '</option>'; 
  1359. ?> 
  1360. </select> 
  1361. <?php 
  1362.  
  1363. /** 
  1364. * Show custom filters to filter orders by status/customer. 
  1365. */ 
  1366. public function shop_order_filters() { 
  1367. $user_string = ''; 
  1368. $user_id = ''; 
  1369. if ( ! empty( $_GET['_customer_user'] ) ) { 
  1370. $user_id = absint( $_GET['_customer_user'] ); 
  1371. $user = get_user_by( 'id', $user_id ); 
  1372. /** translators: 1: user display name 2: user ID 3: user email */ 
  1373. $user_string = sprintf( 
  1374. esc_html__( '%1$s (#%2$s – %3$s)', 'woocommerce' ),  
  1375. $user->display_name,  
  1376. absint( $user->ID ),  
  1377. $user->user_email 
  1378. ); 
  1379. ?> 
  1380. <select class="wc-customer-search" name="_customer_user" data-placeholder="<?php esc_attr_e( 'Search for a customer…', 'woocommerce' ); ?>" data-allow_clear="true"> 
  1381. <option value="<?php echo esc_attr( $user_id ); ?>" selected="selected"><?php echo htmlspecialchars( $user_string ); ?><option> 
  1382. </select> 
  1383. <?php 
  1384.  
  1385. /** 
  1386. * Filters and sorting handler. 
  1387. * 
  1388. * @param array $vars 
  1389. * @return array 
  1390. */ 
  1391. public function request_query( $vars ) { 
  1392. global $typenow, $wp_query, $wp_post_statuses; 
  1393.  
  1394. if ( 'product' === $typenow ) { 
  1395. // Sorting 
  1396. if ( isset( $vars['orderby'] ) ) { 
  1397. if ( 'price' == $vars['orderby'] ) { 
  1398. $vars = array_merge( $vars, array( 
  1399. 'meta_key' => '_price',  
  1400. 'orderby' => 'meta_value_num',  
  1401. ) ); 
  1402. if ( 'sku' == $vars['orderby'] ) { 
  1403. $vars = array_merge( $vars, array( 
  1404. 'meta_key' => '_sku',  
  1405. 'orderby' => 'meta_value',  
  1406. ) ); 
  1407. } elseif ( 'shop_coupon' === $typenow ) { 
  1408.  
  1409. if ( ! empty( $_GET['coupon_type'] ) ) { 
  1410. $vars['meta_key'] = 'discount_type'; 
  1411. $vars['meta_value'] = wc_clean( $_GET['coupon_type'] ); 
  1412. } elseif ( in_array( $typenow, wc_get_order_types( 'order-meta-boxes' ) ) ) { 
  1413.  
  1414. // Filter the orders by the posted customer. 
  1415. if ( isset( $_GET['_customer_user'] ) && $_GET['_customer_user'] > 0 ) { 
  1416. $vars['meta_query'] = array( 
  1417. array( 
  1418. 'key' => '_customer_user',  
  1419. 'value' => (int) $_GET['_customer_user'],  
  1420. 'compare' => '=',  
  1421. ),  
  1422. ); 
  1423.  
  1424. // Sorting 
  1425. if ( isset( $vars['orderby'] ) ) { 
  1426. if ( 'order_total' == $vars['orderby'] ) { 
  1427. $vars = array_merge( $vars, array( 
  1428. 'meta_key' => '_order_total',  
  1429. 'orderby' => 'meta_value_num',  
  1430. ) ); 
  1431.  
  1432. // Status 
  1433. if ( ! isset( $vars['post_status'] ) ) { 
  1434. $post_statuses = wc_get_order_statuses(); 
  1435.  
  1436. foreach ( $post_statuses as $status => $value ) { 
  1437. if ( isset( $wp_post_statuses[ $status ] ) && false === $wp_post_statuses[ $status ]->show_in_admin_all_list ) { 
  1438. unset( $post_statuses[ $status ] ); 
  1439.  
  1440. $vars['post_status'] = array_keys( $post_statuses ); 
  1441.  
  1442. return $vars; 
  1443.  
  1444. /** 
  1445. * Filter the products in admin based on options. 
  1446. * 
  1447. * @param mixed $query 
  1448. */ 
  1449. public function product_filters_query( $query ) { 
  1450. global $typenow, $wp_query; 
  1451.  
  1452. if ( 'product' == $typenow ) { 
  1453.  
  1454. if ( isset( $query->query_vars['product_type'] ) ) { 
  1455. // Subtypes 
  1456. if ( 'downloadable' == $query->query_vars['product_type'] ) { 
  1457. $query->query_vars['product_type'] = ''; 
  1458. $query->is_tax = false; 
  1459. $query->query_vars['meta_value'] = 'yes'; 
  1460. $query->query_vars['meta_key'] = '_downloadable'; 
  1461. } elseif ( 'virtual' == $query->query_vars['product_type'] ) { 
  1462. $query->query_vars['product_type'] = ''; 
  1463. $query->is_tax = false; 
  1464. $query->query_vars['meta_value'] = 'yes'; 
  1465. $query->query_vars['meta_key'] = '_virtual'; 
  1466.  
  1467. // Categories 
  1468. if ( isset( $_GET['product_cat'] ) && '0' === $_GET['product_cat'] ) { 
  1469. $query->query_vars['tax_query'][] = array( 
  1470. 'taxonomy' => 'product_cat',  
  1471. 'field' => 'id',  
  1472. 'terms' => get_terms( 'product_cat', array( 'fields' => 'ids' ) ),  
  1473. 'operator' => 'NOT IN',  
  1474. ); 
  1475.  
  1476. // Shipping classes 
  1477. if ( isset( $_GET['product_shipping_class'] ) && '0' === $_GET['product_shipping_class'] ) { 
  1478. $query->query_vars['tax_query'][] = array( 
  1479. 'taxonomy' => 'product_shipping_class',  
  1480. 'field' => 'id',  
  1481. 'terms' => get_terms( 'product_shipping_class', array( 'fields' => 'ids' ) ),  
  1482. 'operator' => 'NOT IN',  
  1483. ); 
  1484.  
  1485. /** 
  1486. * Search by SKU or ID for products. 
  1487. * 
  1488. * @param string $where 
  1489. * @return string 
  1490. */ 
  1491. public function product_search( $where ) { 
  1492. global $pagenow, $wpdb, $wp; 
  1493.  
  1494. if ( 'edit.php' != $pagenow || ! is_search() || ! isset( $wp->query_vars['s'] ) || 'product' != $wp->query_vars['post_type'] ) { 
  1495. return $where; 
  1496.  
  1497. $search_ids = array(); 
  1498. $terms = explode( ', ', $wp->query_vars['s'] ); 
  1499.  
  1500. foreach ( $terms as $term ) { 
  1501. if ( is_numeric( $term ) ) { 
  1502. $search_ids[] = $term; 
  1503.  
  1504. // Attempt to get a SKU 
  1505. $sku_to_id = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_parent FROM {$wpdb->posts} LEFT JOIN {$wpdb->postmeta} ON {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id WHERE meta_key='_sku' AND meta_value LIKE %s;", '%' . $wpdb->esc_like( wc_clean( $term ) ) . '%' ) ); 
  1506. $sku_to_id = array_merge( wp_list_pluck( $sku_to_id, 'ID' ), wp_list_pluck( $sku_to_id, 'post_parent' ) ); 
  1507.  
  1508. if ( sizeof( $sku_to_id ) > 0 ) { 
  1509. $search_ids = array_merge( $search_ids, $sku_to_id ); 
  1510.  
  1511. $search_ids = array_filter( array_unique( array_map( 'absint', $search_ids ) ) ); 
  1512.  
  1513. if ( sizeof( $search_ids ) > 0 ) { 
  1514. $where = str_replace( 'AND (((', "AND ( ({$wpdb->posts}.ID IN (" . implode( ', ', $search_ids ) . ")) OR ((", $where ); 
  1515.  
  1516. return $where; 
  1517.  
  1518. /** 
  1519. * Disable the auto-save functionality for Orders. 
  1520. */ 
  1521. public function disable_autosave() { 
  1522. global $post; 
  1523.  
  1524. if ( $post && in_array( get_post_type( $post->ID ), wc_get_order_types( 'order-meta-boxes' ) ) ) { 
  1525. wp_dequeue_script( 'autosave' ); 
  1526.  
  1527. /** 
  1528. * Output extra data on post forms. 
  1529. * @param WP_Post $post 
  1530. */ 
  1531. public function edit_form_top( $post ) { 
  1532. echo '<input type="hidden" id="original_post_title" name="original_post_title" value="' . esc_attr( $post->post_title ) . '" />'; 
  1533.  
  1534. /** 
  1535. * Change title boxes in admin. 
  1536. * @param string $text 
  1537. * @param object $post 
  1538. * @return string 
  1539. */ 
  1540. public function enter_title_here( $text, $post ) { 
  1541. switch ( $post->post_type ) { 
  1542. case 'product' : 
  1543. $text = __( 'Product name', 'woocommerce' ); 
  1544. break; 
  1545. case 'shop_coupon' : 
  1546. $text = __( 'Coupon code', 'woocommerce' ); 
  1547. break; 
  1548.  
  1549. return $text; 
  1550.  
  1551. /** 
  1552. * Print coupon description textarea field. 
  1553. * @param WP_Post $post 
  1554. */ 
  1555. public function edit_form_after_title( $post ) { 
  1556. if ( 'shop_coupon' === $post->post_type ) { 
  1557. ?> 
  1558. <textarea id="woocommerce-coupon-description" name="excerpt" cols="5" rows="2" placeholder="<?php esc_attr_e( 'Description (optional)', 'woocommerce' ); ?>"><?php echo $post->post_excerpt; // This is already escaped in core ?></textarea> 
  1559. <?php 
  1560.  
  1561. /** 
  1562. * Hidden default Meta-Boxes. 
  1563. * @param array $hidden 
  1564. * @param object $screen 
  1565. * @return array 
  1566. */ 
  1567. public function hidden_meta_boxes( $hidden, $screen ) { 
  1568. if ( 'product' === $screen->post_type && 'post' === $screen->base ) { 
  1569. $hidden = array_merge( $hidden, array( 'postcustom' ) ); 
  1570.  
  1571. return $hidden; 
  1572.  
  1573. /** 
  1574. * Output product visibility options. 
  1575. */ 
  1576. public function product_data_visibility() { 
  1577. global $post, $thepostid, $product_object; 
  1578.  
  1579. if ( 'product' !== $post->post_type ) { 
  1580. return; 
  1581.  
  1582. $thepostid = $post->ID; 
  1583. $product_object = $thepostid ? wc_get_product( $thepostid ) : new WC_Product; 
  1584. $current_visibility = $product_object->get_catalog_visibility(); 
  1585. $current_featured = wc_bool_to_string( $product_object->get_featured() ); 
  1586. $visibility_options = wc_get_product_visibility_options(); 
  1587. ?> 
  1588. <div class="misc-pub-section" id="catalog-visibility"> 
  1589. <?php _e( 'Catalog visibility:', 'woocommerce' ); ?> <strong id="catalog-visibility-display"><?php 
  1590. echo isset( $visibility_options[ $current_visibility ] ) ? esc_html( $visibility_options[ $current_visibility ] ) : esc_html( $current_visibility ); 
  1591.  
  1592. if ( 'yes' === $current_featured ) { 
  1593. echo ', ' . __( 'Featured', 'woocommerce' ); 
  1594. ?></strong> 
  1595.  
  1596. <a href="#catalog-visibility" class="edit-catalog-visibility hide-if-no-js"><?php _e( 'Edit', 'woocommerce' ); ?></a> 
  1597.  
  1598. <div id="catalog-visibility-select" class="hide-if-js"> 
  1599.  
  1600. <input type="hidden" name="current_visibility" id="current_visibility" value="<?php echo esc_attr( $current_visibility ); ?>" /> 
  1601. <input type="hidden" name="current_featured" id="current_featured" value="<?php echo esc_attr( $current_featured ); ?>" /> 
  1602.  
  1603. <?php 
  1604. echo '<p>' . __( 'Choose where this product should be displayed in your catalog. The product will always be accessible directly.', 'woocommerce' ) . '</p>'; 
  1605.  
  1606. foreach ( $visibility_options as $name => $label ) { 
  1607. 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 />'; 
  1608.  
  1609. echo '<p>' . __( 'Enable this option to feature this product.', 'woocommerce' ) . '</p>'; 
  1610.  
  1611. echo '<input type="checkbox" name="_featured" id="_featured" ' . checked( $current_featured, 'yes', false ) . ' /> <label for="_featured">' . __( 'Featured product', 'woocommerce' ) . '</label><br />'; 
  1612. ?> 
  1613. <p> 
  1614. <a href="#catalog-visibility" class="save-post-visibility hide-if-no-js button"><?php _e( 'OK', 'woocommerce' ); ?></a> 
  1615. <a href="#catalog-visibility" class="cancel-post-visibility hide-if-no-js"><?php _e( 'Cancel', 'woocommerce' ); ?></a> 
  1616. </p> 
  1617. </div> 
  1618. </div> 
  1619. <?php 
  1620.  
  1621. /** 
  1622. * Filter the directory for uploads. 
  1623. * 
  1624. * @param array $pathdata 
  1625. * @return array 
  1626. */ 
  1627. public function upload_dir( $pathdata ) { 
  1628.  
  1629. // Change upload dir for downloadable files 
  1630. if ( isset( $_POST['type'] ) && 'downloadable_product' == $_POST['type'] ) { 
  1631.  
  1632. if ( empty( $pathdata['subdir'] ) ) { 
  1633. $pathdata['path'] = $pathdata['path'] . '/woocommerce_uploads'; 
  1634. $pathdata['url'] = $pathdata['url'] . '/woocommerce_uploads'; 
  1635. $pathdata['subdir'] = '/woocommerce_uploads'; 
  1636. } else { 
  1637. $new_subdir = '/woocommerce_uploads' . $pathdata['subdir']; 
  1638.  
  1639. $pathdata['path'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['path'] ); 
  1640. $pathdata['url'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['url'] ); 
  1641. $pathdata['subdir'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['subdir'] ); 
  1642.  
  1643. return $pathdata; 
  1644.  
  1645. /** 
  1646. * Run a filter when uploading a downloadable product. 
  1647. */ 
  1648. public function woocommerce_media_upload_downloadable_product() { 
  1649. do_action( 'media_upload_file' ); 
  1650.  
  1651. /** 
  1652. * Grant downloadable file access to any newly added files on any existing. 
  1653. * orders for this product that have previously been granted downloadable file access. 
  1654. * 
  1655. * @param int $product_id product identifier 
  1656. * @param int $variation_id optional product variation identifier 
  1657. * @param array $downloadable_files newly set files 
  1658. * @deprecated and moved to post-data class. 
  1659. */ 
  1660. public function process_product_file_download_paths( $product_id, $variation_id, $downloadable_files ) { 
  1661. WC_Post_Data::process_product_file_download_paths( $product_id, $variation_id, $downloadable_files ); 
  1662.  
  1663. /** 
  1664. * Disable DFW feature pointer. 
  1665. */ 
  1666. public function disable_dfw_feature_pointer() { 
  1667. $screen = get_current_screen(); 
  1668.  
  1669. if ( $screen && 'product' === $screen->id && 'post' === $screen->base ) { 
  1670. remove_action( 'admin_print_footer_scripts', array( 'WP_Internal_Pointers', 'pointer_wp410_dfw' ) ); 
  1671.  
  1672. /** 
  1673. * Removes products, orders, and coupons from the list of post types that support "View Mode" switching. 
  1674. * View mode is seen on posts where you can switch between list or excerpt. Our post types don't support 
  1675. * it, so we want to hide the useless UI from the screen options tab. 
  1676. * 
  1677. * @since 2.6 
  1678. * @param array $post_types Array of post types supporting view mode 
  1679. * @return array Array of post types supporting view mode, without products, orders, and coupons 
  1680. */ 
  1681. public function disable_view_mode_options( $post_types ) { 
  1682. unset( $post_types['product'], $post_types['shop_order'], $post_types['shop_coupon'] ); 
  1683. return $post_types; 
  1684.  
  1685. /** 
  1686. * Show blank slate. 
  1687. */ 
  1688. public function maybe_render_blank_state( $which ) { 
  1689. global $post_type; 
  1690.  
  1691. if ( in_array( $post_type, array( 'shop_order', 'product', 'shop_coupon' ) ) && 'bottom' === $which ) { 
  1692. $counts = (array) wp_count_posts( $post_type ); 
  1693. unset( $counts['auto-draft'] ); 
  1694. $count = array_sum( $counts ); 
  1695.  
  1696. if ( 0 < $count ) { 
  1697. return; 
  1698.  
  1699. echo '<div class="woocommerce-BlankState">'; 
  1700.  
  1701. switch ( $post_type ) { 
  1702. case 'shop_order' : 
  1703. ?> 
  1704. <h2 class="woocommerce-BlankState-message"><?php _e( 'When you receive a new order, it will appear here.', 'woocommerce' ); ?></h2> 
  1705. <a class="woocommerce-BlankState-cta button-primary button" target="_blank" href="https://docs.woocommerce.com/document/managing-orders/?utm_source=blankslate&utm_medium=product&utm_content=ordersdoc&utm_campaign=woocommerceplugin"><?php _e( 'Learn more about orders', 'woocommerce' ); ?></a> 
  1706. <?php 
  1707. break; 
  1708. case 'shop_coupon' : 
  1709. ?> 
  1710. <h2 class="woocommerce-BlankState-message"><?php _e( 'Coupons are a great way to offer discounts and rewards to your customers. They will appear here once created.', 'woocommerce' ); ?></h2> 
  1711. <a class="woocommerce-BlankState-cta button-primary button" target="_blank" href="https://docs.woocommerce.com/document/coupon-management/?utm_source=blankslate&utm_medium=product&utm_content=couponsdoc&utm_campaign=woocommerceplugin"><?php _e( 'Learn more about coupons', 'woocommerce' ); ?></a> 
  1712. <?php 
  1713. break; 
  1714. case 'product' : 
  1715. ?> 
  1716. <h2 class="woocommerce-BlankState-message"><?php _e( 'Ready to start selling something awesome?', 'woocommerce' ); ?></h2> 
  1717. <a class="woocommerce-BlankState-cta button-primary button" href="<?php echo esc_url( admin_url( 'post-new.php?post_type=product&tutorial=true' ) ); ?>"><?php _e( 'Create your first product!', 'woocommerce' ); ?></a> 
  1718. <?php 
  1719. break; 
  1720.  
  1721. echo '<style type="text/css">#posts-filter .wp-list-table, #posts-filter .tablenav.top, .tablenav.bottom .actions, .wrap .subsubsub { display: none; } </style></div>'; 
  1722.  
  1723. /** 
  1724. * When editing the shop page, we should hide templates. 
  1725. * @return array 
  1726. */ 
  1727. public function hide_cpt_archive_templates( $page_templates, $class, $post ) { 
  1728. $shop_page_id = wc_get_page_id( 'shop' ); 
  1729.  
  1730. if ( $post && absint( $shop_page_id ) === absint( $post->ID ) ) { 
  1731. $page_templates = array(); 
  1732.  
  1733. return $page_templates; 
  1734.  
  1735. /** 
  1736. * Show a notice above the CPT archive. 
  1737. */ 
  1738. public function show_cpt_archive_notice( $post ) { 
  1739. $shop_page_id = wc_get_page_id( 'shop' ); 
  1740.  
  1741. if ( $post && absint( $shop_page_id ) === absint( $post->ID ) ) { 
  1742. ?> 
  1743. <div class="notice notice-info"> 
  1744. <p><?php printf( __( 'This is the WooCommerce shop page. The shop page is a special archive that lists your products. <a href="%s">You can read more about this here</a>.', 'woocommerce' ), 'https://docs.woocommerce.com/document/woocommerce-pages/#section-4' ); ?></p> 
  1745. </div> 
  1746. <?php 
  1747.  
  1748. endif; 
  1749.  
  1750. new WC_Admin_Post_Types(); 
.