PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.8.1
WooCommerce v10.8.1
10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / includes / admin / class-wc-admin-post-types.php
woocommerce / includes / admin Last commit date
helper 4 weeks ago importers 1 year ago list-tables 4 months ago marketplace-suggestions 10 months ago meta-boxes 4 weeks ago notes 4 weeks ago plugin-updates 2 years ago reports 2 months ago settings 1 week ago views 2 months ago class-wc-admin-addons.php 7 months ago class-wc-admin-api-keys-table-list.php 2 years ago class-wc-admin-api-keys.php 10 months ago class-wc-admin-assets.php 4 weeks ago class-wc-admin-attributes.php 3 years ago class-wc-admin-brands.php 3 months ago class-wc-admin-customize.php 5 years ago class-wc-admin-dashboard-setup.php 10 months ago class-wc-admin-dashboard.php 3 months ago class-wc-admin-duplicate-product.php 4 months ago class-wc-admin-exporters.php 1 year ago class-wc-admin-help.php 2 years ago class-wc-admin-importers.php 10 months ago class-wc-admin-log-table-list.php 3 months ago class-wc-admin-marketplace-promotions.php 3 months ago class-wc-admin-menus.php 3 months ago class-wc-admin-meta-boxes.php 1 year ago class-wc-admin-notices.php 4 weeks ago class-wc-admin-permalink-settings.php 5 years ago class-wc-admin-pointers.php 3 years ago class-wc-admin-post-types.php 1 year ago class-wc-admin-profile.php 1 year ago class-wc-admin-reports.php 3 months ago class-wc-admin-settings.php 2 months ago class-wc-admin-setup-wizard.php 3 months ago class-wc-admin-status.php 1 year ago class-wc-admin-taxonomies.php 6 months ago class-wc-admin-upload-downloadable-product.php 2 years ago class-wc-admin-webhooks-table-list.php 1 year ago class-wc-admin-webhooks.php 10 months ago class-wc-admin.php 2 months ago wc-admin-functions.php 6 months ago wc-meta-box-functions.php 1 year ago woocommerce-legacy-reports.php 1 year ago
class-wc-admin-post-types.php
1008 lines
1 <?php
2 /**
3 * Post Types Admin
4 *
5 * @package WooCommerce\Admin
6 * @version 3.3.0
7 */
8
9 use Automattic\Jetpack\Constants;
10 use Automattic\WooCommerce\Enums\ProductStockStatus;
11 use Automattic\WooCommerce\Enums\ProductType;
12 use Automattic\WooCommerce\Internal\CostOfGoodsSold\CostOfGoodsSoldController;
13 use Automattic\WooCommerce\Utilities\NumberUtil;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit;
17 }
18
19 if ( class_exists( 'WC_Admin_Post_Types', false ) ) {
20 new WC_Admin_Post_Types();
21 return;
22 }
23
24 /**
25 * WC_Admin_Post_Types Class.
26 *
27 * Handles the edit posts views and some functionality on the edit post screen for WC post types.
28 */
29 class WC_Admin_Post_Types {
30
31 /**
32 * Constructor.
33 */
34 public function __construct() {
35 include_once __DIR__ . '/class-wc-admin-meta-boxes.php';
36
37 if ( ! function_exists( 'duplicate_post_plugin_activation' ) ) {
38 include_once __DIR__ . '/class-wc-admin-duplicate-product.php';
39 }
40
41 // Load correct list table classes for current screen.
42 add_action( 'current_screen', array( $this, 'setup_screen' ) );
43 add_action( 'check_ajax_referer', array( $this, 'setup_screen' ) );
44
45 // Admin notices.
46 add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) );
47 add_filter( 'woocommerce_order_updated_messages', array( $this, 'order_updated_messages' ) );
48 add_filter( 'bulk_post_updated_messages', array( $this, 'bulk_post_updated_messages' ), 10, 2 );
49 add_action(
50 'admin_notices',
51 function () {
52 $this->maybe_display_warning_for_password_protected_coupon();
53 }
54 );
55
56 // Disable Auto Save.
57 add_action( 'admin_print_scripts', array( $this, 'disable_autosave' ) );
58
59 // Extra post data and screen elements.
60 add_action( 'edit_form_top', array( $this, 'edit_form_top' ) );
61 add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 );
62 add_action( 'edit_form_after_title', array( $this, 'edit_form_after_title' ) );
63 add_filter( 'default_hidden_meta_boxes', array( $this, 'hidden_meta_boxes' ), 10, 2 );
64 add_action( 'post_submitbox_misc_actions', array( $this, 'product_data_visibility' ) );
65
66 include_once __DIR__ . '/class-wc-admin-upload-downloadable-product.php';
67
68 // Hide template for CPT archive.
69 add_filter( 'theme_page_templates', array( $this, 'hide_cpt_archive_templates' ), 10, 3 );
70 add_action( 'edit_form_top', array( $this, 'show_cpt_archive_notice' ) );
71
72 // Add a post display state for special WC pages.
73 add_filter( 'display_post_states', array( $this, 'add_display_post_states' ), 10, 2 );
74
75 // Bulk / quick edit.
76 add_action( 'bulk_edit_custom_box', array( $this, 'bulk_edit' ), 10, 2 );
77 add_action( 'quick_edit_custom_box', array( $this, 'quick_edit' ), 10, 2 );
78 add_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ), 10, 2 );
79 add_action( 'woocommerce_product_bulk_and_quick_edit', array( $this, 'bulk_and_quick_edit_save_post' ), 10, 2 );
80 }
81
82 /**
83 * Looks at the current screen and loads the correct list table handler.
84 *
85 * @since 3.3.0
86 */
87 public function setup_screen() {
88 global $wc_list_table;
89
90 $request_data = $this->request_data();
91
92 $screen_id = false;
93
94 if ( function_exists( 'get_current_screen' ) ) {
95 $screen = get_current_screen();
96 $screen_id = isset( $screen, $screen->id ) ? $screen->id : '';
97 }
98
99 if ( ! empty( $request_data['screen'] ) ) {
100 $screen_id = wc_clean( wp_unslash( $request_data['screen'] ) );
101 }
102
103 switch ( $screen_id ) {
104 case 'edit-shop_order':
105 include_once __DIR__ . '/list-tables/class-wc-admin-list-table-orders.php';
106 $wc_list_table = new WC_Admin_List_Table_Orders();
107 break;
108 case 'edit-shop_coupon':
109 include_once __DIR__ . '/list-tables/class-wc-admin-list-table-coupons.php';
110 $wc_list_table = new WC_Admin_List_Table_Coupons();
111 break;
112 case 'edit-product':
113 include_once __DIR__ . '/list-tables/class-wc-admin-list-table-products.php';
114 $wc_list_table = new WC_Admin_List_Table_Products();
115 break;
116 }
117
118 // Ensure the table handler is only loaded once. Prevents multiple loads if a plugin calls check_ajax_referer many times.
119 remove_action( 'current_screen', array( $this, 'setup_screen' ) );
120 remove_action( 'check_ajax_referer', array( $this, 'setup_screen' ) );
121 }
122
123 /**
124 * Change messages when a post type is updated.
125 *
126 * @param array $messages Array of messages.
127 * @return array
128 */
129 public function post_updated_messages( $messages ) {
130 global $post;
131
132 $messages['product'] = array(
133 0 => '', // Unused. Messages start at index 1.
134 /* translators: %1$s: Product link opening tag. %2$s: Product link closing tag.*/
135 1 => sprintf( __( 'Product updated. %1$sView Product%2$s', 'woocommerce' ), '<a id="woocommerce-product-updated-message-view-product__link" href="' . esc_url( get_permalink( $post->ID ) ) . '">', '</a>' ),
136 2 => __( 'Custom field updated.', 'woocommerce' ),
137 3 => __( 'Custom field deleted.', 'woocommerce' ),
138 4 => __( 'Product updated.', 'woocommerce' ),
139 5 => __( 'Revision restored.', 'woocommerce' ),
140 /* translators: %1$s: Product link opening tag. %2$s: Product link closing tag.*/
141 6 => sprintf( __( 'Product published. %1$sView Product%2$s', 'woocommerce' ), '<a id="woocommerce-product-updated-message-view-product__link" href="' . esc_url( get_permalink( $post->ID ) ) . '">', '</a>' ),
142 7 => __( 'Product saved.', 'woocommerce' ),
143 /* translators: %s: product url */
144 8 => sprintf( __( 'Product submitted. <a target="_blank" href="%s">Preview product</a>', 'woocommerce' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) ),
145 9 => sprintf(
146 /* translators: 1: date 2: product url */
147 __( 'Product scheduled for: %1$s. <a target="_blank" href="%2$s">Preview product</a>', 'woocommerce' ),
148 '<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ) . '</strong>',
149 esc_url( get_permalink( $post->ID ) )
150 ),
151 /* translators: %s: product url */
152 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 ) ) ) ),
153 );
154
155 $messages = $this->order_updated_messages( $messages );
156
157 $messages['shop_coupon'] = array(
158 0 => '', // Unused. Messages start at index 1.
159 1 => __( 'Coupon updated.', 'woocommerce' ),
160 2 => __( 'Custom field updated.', 'woocommerce' ),
161 3 => __( 'Custom field deleted.', 'woocommerce' ),
162 4 => __( 'Coupon updated.', 'woocommerce' ),
163 5 => __( 'Revision restored.', 'woocommerce' ),
164 6 => __( 'Coupon updated.', 'woocommerce' ),
165 7 => __( 'Coupon saved.', 'woocommerce' ),
166 8 => __( 'Coupon submitted.', 'woocommerce' ),
167 9 => sprintf(
168 /* translators: %s: date */
169 __( 'Coupon scheduled for: %s.', 'woocommerce' ),
170 '<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ) . '</strong>'
171 ),
172 10 => __( 'Coupon draft updated.', 'woocommerce' ),
173 );
174
175 return $messages;
176 }
177
178 /**
179 * Add messages when an order is updated.
180 *
181 * @param array $messages Array of messages.
182 *
183 * @return array
184 */
185 public function order_updated_messages( array $messages ) {
186 global $post, $theorder;
187
188 if ( ! isset( $theorder ) || ! $theorder instanceof WC_Abstract_Order ) {
189 if ( ! isset( $post ) || 'shop_order' !== $post->post_type ) {
190 return $messages;
191 } else {
192 \Automattic\WooCommerce\Utilities\OrderUtil::init_theorder_object( $post );
193 }
194 }
195
196 $messages['shop_order'] = array(
197 0 => '', // Unused. Messages start at index 1.
198 1 => __( 'Order updated.', 'woocommerce' ),
199 2 => __( 'Custom field updated.', 'woocommerce' ),
200 3 => __( 'Custom field deleted.', 'woocommerce' ),
201 4 => __( 'Order updated.', 'woocommerce' ),
202 5 => __( 'Revision restored.', 'woocommerce' ),
203 6 => __( 'Order updated.', 'woocommerce' ),
204 7 => __( 'Order saved.', 'woocommerce' ),
205 8 => __( 'Order submitted.', 'woocommerce' ),
206 9 => sprintf(
207 /* translators: %s: date */
208 __( 'Order scheduled for: %s.', 'woocommerce' ),
209 '<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $theorder->get_date_created() ?? $post->post_date ) ) . '</strong>'
210 ),
211 10 => __( 'Order draft updated.', 'woocommerce' ),
212 11 => __( 'Order updated and sent.', 'woocommerce' ),
213 );
214
215 return $messages;
216 }
217
218 /**
219 * Specify custom bulk actions messages for different post types.
220 *
221 * @param array $bulk_messages Array of messages.
222 * @param array $bulk_counts Array of how many objects were updated.
223 * @return array
224 */
225 public function bulk_post_updated_messages( $bulk_messages, $bulk_counts ) {
226 $bulk_messages['product'] = array(
227 /* translators: %s: product count */
228 'updated' => _n( '%s product updated.', '%s products updated.', $bulk_counts['updated'], 'woocommerce' ),
229 /* translators: %s: product count */
230 'locked' => _n( '%s product not updated, somebody is editing it.', '%s products not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
231 /* translators: %s: product count */
232 'deleted' => _n( '%s product permanently deleted.', '%s products permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
233 /* translators: %s: product count */
234 'trashed' => _n( '%s product moved to the Trash.', '%s products moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
235 /* translators: %s: product count */
236 'untrashed' => _n( '%s product restored from the Trash.', '%s products restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
237 );
238
239 $bulk_messages['shop_order'] = array(
240 /* translators: %s: order count */
241 'updated' => _n( '%s order updated.', '%s orders updated.', $bulk_counts['updated'], 'woocommerce' ),
242 /* translators: %s: order count */
243 'locked' => _n( '%s order not updated, somebody is editing it.', '%s orders not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
244 /* translators: %s: order count */
245 'deleted' => _n( '%s order permanently deleted.', '%s orders permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
246 /* translators: %s: order count */
247 'trashed' => _n( '%s order moved to the Trash.', '%s orders moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
248 /* translators: %s: order count */
249 'untrashed' => _n( '%s order restored from the Trash.', '%s orders restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
250 );
251
252 $bulk_messages['shop_coupon'] = array(
253 /* translators: %s: coupon count */
254 'updated' => _n( '%s coupon updated.', '%s coupons updated.', $bulk_counts['updated'], 'woocommerce' ),
255 /* translators: %s: coupon count */
256 'locked' => _n( '%s coupon not updated, somebody is editing it.', '%s coupons not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
257 /* translators: %s: coupon count */
258 'deleted' => _n( '%s coupon permanently deleted.', '%s coupons permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
259 /* translators: %s: coupon count */
260 'trashed' => _n( '%s coupon moved to the Trash.', '%s coupons moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
261 /* translators: %s: coupon count */
262 'untrashed' => _n( '%s coupon restored from the Trash.', '%s coupons restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
263 );
264
265 return $bulk_messages;
266 }
267
268 /**
269 * Shows a warning when editing a password-protected coupon.
270 *
271 * @since 9.2.0
272 */
273 private function maybe_display_warning_for_password_protected_coupon() {
274 if ( ! function_exists( 'get_current_screen' ) || 'shop_coupon' !== get_current_screen()->id ) {
275 return;
276 }
277
278 if ( ! isset( $GLOBALS['post'] ) || 'shop_coupon' !== $GLOBALS['post']->post_type ) {
279 return;
280 }
281
282 wp_admin_notice(
283 __(
284 'This coupon is password protected. WooCommerce does not support password protection for coupons. You can temporarily hide a coupon by making it private. Alternatively, usage limits and restrictions can be configured below.',
285 'woocommerce'
286 ),
287 array(
288 'type' => 'warning',
289 'id' => 'wc-password-protected-coupon-warning',
290 'additional_classes' => empty( $GLOBALS['post']->post_password ) ? array( 'hidden' ) : array(),
291 )
292 );
293 }
294
295 /**
296 * Custom bulk edit - form.
297 *
298 * @param string $column_name Column being shown.
299 * @param string $post_type Post type being shown.
300 */
301 public function bulk_edit( $column_name, $post_type ) {
302 if ( 'price' !== $column_name || 'product' !== $post_type ) {
303 return;
304 }
305
306 $shipping_class = get_terms(
307 'product_shipping_class',
308 array(
309 'hide_empty' => false,
310 )
311 );
312
313 include WC()->plugin_path() . '/includes/admin/views/html-bulk-edit-product.php';
314 }
315
316 /**
317 * Custom quick edit - form.
318 *
319 * @param string $column_name Column being shown.
320 * @param string $post_type Post type being shown.
321 */
322 public function quick_edit( $column_name, $post_type ) {
323 if ( 'price' !== $column_name || 'product' !== $post_type ) {
324 return;
325 }
326
327 $shipping_class = get_terms(
328 'product_shipping_class',
329 array(
330 'hide_empty' => false,
331 )
332 );
333
334 include WC()->plugin_path() . '/includes/admin/views/html-quick-edit-product.php';
335 }
336
337 /**
338 * Offers a way to hook into save post without causing an infinite loop
339 * when quick/bulk saving product info.
340 *
341 * @since 3.0.0
342 * @param int $post_id Post ID being saved.
343 * @param object $post Post object being saved.
344 */
345 public function bulk_and_quick_edit_hook( $post_id, $post ) {
346 remove_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ) );
347 do_action( 'woocommerce_product_bulk_and_quick_edit', $post_id, $post );
348 add_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ), 10, 2 );
349 }
350
351 /**
352 * Quick and bulk edit saving.
353 *
354 * @param int $post_id Post ID being saved.
355 * @param object $post Post object being saved.
356 * @return int
357 */
358 public function bulk_and_quick_edit_save_post( $post_id, $post ) {
359 $request_data = $this->request_data();
360
361 // If this is an autosave, our form has not been submitted, so we don't want to do anything.
362 if ( Constants::is_true( 'DOING_AUTOSAVE' ) ) {
363 return $post_id;
364 }
365
366 // Don't save revisions and autosaves.
367 if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) || 'product' !== $post->post_type || ! current_user_can( 'edit_post', $post_id ) ) {
368 return $post_id;
369 }
370
371 // Check nonce.
372 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
373 if ( ! isset( $request_data['woocommerce_quick_edit_nonce'] ) || ! wp_verify_nonce( $request_data['woocommerce_quick_edit_nonce'], 'woocommerce_quick_edit_nonce' ) ) {
374 return $post_id;
375 }
376
377 // Get the product and save.
378 $product = wc_get_product( $post );
379
380 if ( ! empty( $request_data['woocommerce_quick_edit'] ) ) { // WPCS: input var ok.
381 $this->quick_edit_save( $post_id, $product );
382 } else {
383 $this->bulk_edit_save( $post_id, $product );
384 }
385
386 return $post_id;
387 }
388
389 /**
390 * Quick edit.
391 *
392 * @param int $post_id Post ID being saved.
393 * @param WC_Product $product Product object.
394 */
395 private function quick_edit_save( $post_id, $product ) {
396 $request_data = $this->request_data();
397
398 $data_store = $product->get_data_store();
399 $old_regular_price = $product->get_regular_price();
400 $old_sale_price = $product->get_sale_price();
401 $input_to_props = array(
402 '_weight' => 'weight',
403 '_length' => 'length',
404 '_width' => 'width',
405 '_height' => 'height',
406 '_visibility' => 'catalog_visibility',
407 '_tax_class' => 'tax_class',
408 '_tax_status' => 'tax_status',
409 );
410
411 foreach ( $input_to_props as $input_var => $prop ) {
412 if ( isset( $request_data[ $input_var ] ) ) {
413 $product->{"set_{$prop}"}( wc_clean( wp_unslash( $request_data[ $input_var ] ) ) );
414 }
415 }
416
417 if ( isset( $request_data['_sku'] ) ) {
418 $sku = $product->get_sku();
419 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
420 $new_sku = (string) wc_clean( $request_data['_sku'] );
421
422 if ( $new_sku !== $sku ) {
423 if ( ! empty( $new_sku ) ) {
424 $unique_sku = wc_product_has_unique_sku( $post_id, $new_sku );
425 if ( $unique_sku ) {
426 $product->set_sku( wc_clean( wp_unslash( $new_sku ) ) );
427 }
428 } else {
429 $product->set_sku( '' );
430 }
431 }
432 }
433
434 if ( ! empty( $request_data['_shipping_class'] ) ) {
435 if ( '_no_shipping_class' === $request_data['_shipping_class'] ) {
436 $product->set_shipping_class_id( 0 );
437 } else {
438 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
439 $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $request_data['_shipping_class'] ) );
440 $product->set_shipping_class_id( $shipping_class_id );
441 }
442 }
443
444 if ( ! empty( $request_data['_tax_class'] ) ) {
445 $tax_class = sanitize_title( wp_unslash( $request_data['_tax_class'] ) );
446 if ( 'standard' === $tax_class ) {
447 $tax_class = '';
448 }
449 $product->set_tax_class( $tax_class );
450 }
451
452 $product->set_featured( isset( $request_data['_featured'] ) );
453
454 if ( $product->is_type( ProductType::SIMPLE ) || $product->is_type( ProductType::EXTERNAL ) ) {
455
456 if ( isset( $request_data['_regular_price'] ) ) {
457 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
458 $new_regular_price = ( '' === $request_data['_regular_price'] ) ? '' : wc_format_decimal( $request_data['_regular_price'] );
459 $product->set_regular_price( $new_regular_price );
460 } else {
461 $new_regular_price = null;
462 }
463
464 if ( isset( $request_data['_sale_price'] ) ) {
465 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
466 $new_sale_price = ( '' === $request_data['_sale_price'] ) ? '' : wc_format_decimal( $request_data['_sale_price'] );
467 $product->set_sale_price( $new_sale_price );
468 } else {
469 $new_sale_price = null;
470 }
471
472 // Handle price - remove dates and set to lowest.
473 $price_changed = false;
474
475 if ( ! is_null( $new_regular_price ) && $new_regular_price !== $old_regular_price ) {
476 $price_changed = true;
477 } elseif ( ! is_null( $new_sale_price ) && $new_sale_price !== $old_sale_price ) {
478 $price_changed = true;
479 }
480
481 if ( $price_changed ) {
482 $product->set_date_on_sale_to( '' );
483 $product->set_date_on_sale_from( '' );
484 }
485 }
486
487 if ( wc_get_container()->get( CostOfGoodsSoldController::class )->feature_is_enabled() && isset( $request_data['_cogs_value'] ) ) {
488 $cogs_value = $request_data['_cogs_value'];
489 $cogs_value = '' === $cogs_value ? null : (float) wc_format_decimal( $cogs_value );
490 $product->set_cogs_value( $cogs_value );
491 }
492
493 // Handle Stock Data.
494 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
495 $manage_stock = ! empty( $request_data['_manage_stock'] ) && ProductType::GROUPED !== $product->get_type() ? 'yes' : 'no';
496 $backorders = ! empty( $request_data['_backorders'] ) ? wc_clean( $request_data['_backorders'] ) : 'no';
497 if ( ! empty( $request_data['_stock_status'] ) ) {
498 $stock_status = wc_clean( $request_data['_stock_status'] );
499 } else {
500 $stock_status = $product->is_type( ProductType::VARIABLE ) ? null : ProductStockStatus::IN_STOCK;
501 }
502 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
503
504 $product->set_manage_stock( $manage_stock );
505
506 if ( ProductType::EXTERNAL !== $product->get_type() ) {
507 $product->set_backorders( $backorders );
508 }
509
510 if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
511 $stock_amount = 'yes' === $manage_stock && isset( $request_data['_stock'] ) && is_numeric( wp_unslash( $request_data['_stock'] ) ) ? wc_stock_amount( wp_unslash( $request_data['_stock'] ) ) : '';
512 $product->set_stock_quantity( $stock_amount );
513 }
514
515 $product = $this->maybe_update_stock_status( $product, $stock_status );
516
517 $product->save();
518
519 do_action( 'woocommerce_product_quick_edit_save', $product );
520 }
521
522 /**
523 * Bulk edit.
524 *
525 * @param int $post_id Post ID being saved.
526 * @param WC_Product $product Product object.
527 */
528 public function bulk_edit_save( $post_id, $product ) {
529 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
530
531 $request_data = $this->request_data();
532
533 $data_store = $product->get_data_store();
534
535 if ( ! empty( $request_data['change_weight'] ) && isset( $request_data['_weight'] ) ) {
536 $product->set_weight( wc_clean( wp_unslash( $request_data['_weight'] ) ) );
537 }
538
539 if ( ! empty( $request_data['change_dimensions'] ) ) {
540 if ( isset( $request_data['_length'] ) ) {
541 $product->set_length( wc_clean( wp_unslash( $request_data['_length'] ) ) );
542 }
543 if ( isset( $request_data['_width'] ) ) {
544 $product->set_width( wc_clean( wp_unslash( $request_data['_width'] ) ) );
545 }
546 if ( isset( $request_data['_height'] ) ) {
547 $product->set_height( wc_clean( wp_unslash( $request_data['_height'] ) ) );
548 }
549 }
550
551 if ( ! empty( $request_data['_tax_status'] ) ) {
552 $product->set_tax_status( wc_clean( $request_data['_tax_status'] ) );
553 }
554
555 if ( ! empty( $request_data['_tax_class'] ) ) {
556 $tax_class = sanitize_title( wp_unslash( $request_data['_tax_class'] ) );
557 if ( 'standard' === $tax_class ) {
558 $tax_class = '';
559 }
560 $product->set_tax_class( $tax_class );
561 }
562
563 if ( ! empty( $request_data['_shipping_class'] ) ) {
564 if ( '_no_shipping_class' === $request_data['_shipping_class'] ) {
565 $product->set_shipping_class_id( 0 );
566 } else {
567 $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $request_data['_shipping_class'] ) );
568 $product->set_shipping_class_id( $shipping_class_id );
569 }
570 }
571
572 if ( ! empty( $request_data['_visibility'] ) ) {
573 $product->set_catalog_visibility( wc_clean( $request_data['_visibility'] ) );
574 }
575
576 if ( ! empty( $request_data['_featured'] ) ) {
577 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
578 $product->set_featured( wp_unslash( $request_data['_featured'] ) );
579 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
580 }
581
582 if ( ! empty( $request_data['_sold_individually'] ) ) {
583 if ( 'yes' === $request_data['_sold_individually'] ) {
584 $product->set_sold_individually( 'yes' );
585 } else {
586 $product->set_sold_individually( '' );
587 }
588 }
589
590 /**
591 * Handle price - remove dates and set to lowest.
592 *
593 * @param array $product_types Array of product types that can change price.
594 *
595 * @since 3.0.0
596 */
597 $change_price_product_types = apply_filters( 'woocommerce_bulk_edit_save_price_product_types', array( ProductType::SIMPLE, ProductType::EXTERNAL ) );
598 $can_product_type_change_price = false;
599 foreach ( $change_price_product_types as $product_type ) {
600 if ( $product->is_type( $product_type ) ) {
601 $can_product_type_change_price = true;
602 break;
603 }
604 }
605
606 if ( $can_product_type_change_price ) {
607 $regular_price_changed = $this->set_new_price( $product, 'regular' );
608 $sale_price_changed = $this->set_new_price( $product, 'sale' );
609
610 if ( $regular_price_changed || $sale_price_changed ) {
611 $product->set_date_on_sale_to( '' );
612 $product->set_date_on_sale_from( '' );
613
614 if ( $product->get_regular_price() < $product->get_sale_price() ) {
615 $product->set_sale_price( '' );
616 }
617 }
618 }
619
620 // Handle Stock Data.
621 $was_managing_stock = $product->get_manage_stock() ? 'yes' : 'no';
622 $backorders = $product->get_backorders();
623 $backorders = ! empty( $request_data['_backorders'] ) ? wc_clean( $request_data['_backorders'] ) : $backorders;
624
625 if ( ! empty( $request_data['_manage_stock'] ) ) {
626 $manage_stock = 'yes' === wc_clean( $request_data['_manage_stock'] ) && ProductType::GROUPED !== $product->get_type() ? 'yes' : 'no';
627 } else {
628 $manage_stock = $was_managing_stock;
629 }
630
631 $stock_amount = 'yes' === $manage_stock && ! empty( $request_data['change_stock'] ) && isset( $request_data['_stock'] ) ? wc_stock_amount( $request_data['_stock'] ) : $product->get_stock_quantity();
632
633 $product->set_manage_stock( $manage_stock );
634
635 if ( ProductType::EXTERNAL !== $product->get_type() ) {
636 $product->set_backorders( $backorders );
637 }
638
639 if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
640 $change_stock = absint( $request_data['change_stock'] );
641 switch ( $change_stock ) {
642 case 2:
643 wc_update_product_stock( $product, $stock_amount, 'increase', true );
644 break;
645 case 3:
646 wc_update_product_stock( $product, $stock_amount, 'decrease', true );
647 break;
648 default:
649 wc_update_product_stock( $product, $stock_amount, 'set', true );
650 break;
651 }
652 } else {
653 // Reset values if WooCommerce Setting - Manage Stock status is disabled.
654 $product->set_stock_quantity( '' );
655 $product->set_manage_stock( 'no' );
656 }
657
658 $stock_status = empty( $request_data['_stock_status'] ) ? null : wc_clean( $request_data['_stock_status'] );
659 $product = $this->maybe_update_stock_status( $product, $stock_status );
660
661 if ( wc_get_container()->get( CostOfGoodsSoldController::class )->feature_is_enabled() ) {
662 $this->maybe_update_cogs_value( $product, $request_data );
663 }
664
665 $product->save();
666
667 do_action( 'woocommerce_product_bulk_edit_save', $product );
668
669 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
670 }
671
672 /**
673 * Disable the auto-save functionality for Orders.
674 */
675 public function disable_autosave() {
676 global $post;
677
678 if ( $post instanceof WP_Post && in_array( get_post_type( $post->ID ), wc_get_order_types( 'order-meta-boxes' ), true ) ) {
679 wp_dequeue_script( 'autosave' );
680 }
681 }
682
683 /**
684 * Output extra data on post forms.
685 *
686 * @param WP_Post $post Current post object.
687 */
688 public function edit_form_top( $post ) {
689 echo '<input type="hidden" id="original_post_title" name="original_post_title" value="' . esc_attr( $post->post_title ) . '" />';
690 }
691
692 /**
693 * Change title boxes in admin.
694 *
695 * @param string $text Text to shown.
696 * @param WP_Post $post Current post object.
697 * @return string
698 */
699 public function enter_title_here( $text, $post ) {
700 switch ( $post->post_type ) {
701 case 'product':
702 $text = esc_html__( 'Product name', 'woocommerce' );
703 break;
704 case 'shop_coupon':
705 $text = esc_html__( 'Coupon code', 'woocommerce' );
706 break;
707 }
708 return $text;
709 }
710
711 /**
712 * Print coupon description textarea field.
713 *
714 * @param WP_Post $post Current post object.
715 */
716 public function edit_form_after_title( $post ) {
717 // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
718 if ( 'shop_coupon' === $post->post_type ) {
719 ?>
720 <textarea id="woocommerce-coupon-description" name="excerpt" cols="5" rows="2" placeholder="<?php esc_attr_e( 'Description (optional)', 'woocommerce' ); ?>"><?php echo $post->post_excerpt; ?></textarea>
721 <?php
722 }
723 // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
724 }
725
726 /**
727 * Hidden default Meta-Boxes.
728 *
729 * @param array $hidden Hidden boxes.
730 * @param object $screen Current screen.
731 * @return array
732 */
733 public function hidden_meta_boxes( $hidden, $screen ) {
734 if ( 'product' === $screen->post_type && 'post' === $screen->base ) {
735 $hidden = array_merge( $hidden, array( 'postcustom' ) );
736 }
737
738 return $hidden;
739 }
740
741 /**
742 * Output product visibility options.
743 */
744 public function product_data_visibility() {
745 global $post, $thepostid, $product_object;
746
747 if ( 'product' !== $post->post_type ) {
748 return;
749 }
750
751 $thepostid = $post->ID;
752 $product_object = $thepostid ? wc_get_product( $thepostid ) : new WC_Product();
753 $current_visibility = $product_object->get_catalog_visibility();
754 $current_featured = wc_bool_to_string( $product_object->get_featured() );
755 $visibility_options = wc_get_product_visibility_options();
756 ?>
757 <div class="misc-pub-section" id="catalog-visibility">
758 <?php esc_html_e( 'Catalog visibility:', 'woocommerce' ); ?>
759 <strong id="catalog-visibility-display">
760 <?php
761
762 echo isset( $visibility_options[ $current_visibility ] ) ? esc_html( $visibility_options[ $current_visibility ] ) : esc_html( $current_visibility );
763
764 if ( 'yes' === $current_featured ) {
765 echo ', ' . esc_html__( 'Featured', 'woocommerce' );
766 }
767 ?>
768 </strong>
769
770 <a href="#catalog-visibility" class="edit-catalog-visibility hide-if-no-js"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a>
771
772 <div id="catalog-visibility-select" class="hide-if-js">
773
774 <input type="hidden" name="current_visibility" id="current_visibility" value="<?php echo esc_attr( $current_visibility ); ?>" />
775 <input type="hidden" name="current_featured" id="current_featured" value="<?php echo esc_attr( $current_featured ); ?>" />
776
777 <?php
778 echo '<p>' . esc_html__( 'This setting determines which shop pages products will be listed on.', 'woocommerce' ) . '</p>';
779
780 foreach ( $visibility_options as $name => $label ) {
781 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 />';
782 }
783
784 echo '<br /><input type="checkbox" name="_featured" id="_featured" ' . checked( $current_featured, 'yes', false ) . ' /> <label for="_featured">' . esc_html__( 'This is a featured product', 'woocommerce' ) . '</label><br />';
785 ?>
786 <p>
787 <a href="#catalog-visibility" class="save-post-visibility hide-if-no-js button"><?php esc_html_e( 'OK', 'woocommerce' ); ?></a>
788 <a href="#catalog-visibility" class="cancel-post-visibility hide-if-no-js"><?php esc_html_e( 'Cancel', 'woocommerce' ); ?></a>
789 </p>
790 </div>
791 </div>
792 <?php
793 }
794
795 /**
796 * Grant downloadable file access to any newly added files on any existing.
797 * orders for this product that have previously been granted downloadable file access.
798 *
799 * @param int $product_id product identifier.
800 * @param int $variation_id optional product variation identifier.
801 * @param array $downloadable_files newly set files.
802 * @deprecated 3.3.0 and moved to post-data class.
803 */
804 public function process_product_file_download_paths( $product_id, $variation_id, $downloadable_files ) {
805 wc_deprecated_function( 'WC_Admin_Post_Types::process_product_file_download_paths', '3.3', '' );
806 WC_Post_Data::process_product_file_download_paths( $product_id, $variation_id, $downloadable_files );
807 }
808
809 /**
810 * When editing the shop page, we should hide templates.
811 *
812 * @param array $page_templates Templates array.
813 * @param string $theme Classname.
814 * @param WP_Post $post The current post object.
815 * @return array
816 */
817 public function hide_cpt_archive_templates( $page_templates, $theme, $post ) {
818 $shop_page_id = wc_get_page_id( 'shop' );
819
820 if ( $post && absint( $post->ID ) === $shop_page_id ) {
821 $page_templates = array();
822 }
823
824 return $page_templates;
825 }
826
827 /**
828 * Show a notice above the CPT archive.
829 *
830 * @param WP_Post $post The current post object.
831 */
832 public function show_cpt_archive_notice( $post ) {
833 $shop_page_id = wc_get_page_id( 'shop' );
834
835 if ( $post && absint( $post->ID ) === $shop_page_id ) {
836 echo '<div class="notice notice-info">';
837 /* translators: %s: URL to read more about the shop page. */
838 echo '<p>' . sprintf( wp_kses_post( __( '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://woocommerce.com/document/woocommerce-pages/#section-4' ) . '</p>';
839 echo '</div>';
840 }
841 }
842
843 /**
844 * Add a post display state for special WC pages in the page list table.
845 *
846 * @param array $post_states An array of post display states.
847 * @param WP_Post $post The current post object.
848 */
849 public function add_display_post_states( $post_states, $post ) {
850 if ( wc_get_page_id( 'shop' ) === $post->ID ) {
851 $post_states['wc_page_for_shop'] = __( 'Shop Page', 'woocommerce' );
852 }
853
854 if ( wc_get_page_id( 'cart' ) === $post->ID ) {
855 $post_states['wc_page_for_cart'] = __( 'Cart Page', 'woocommerce' );
856 }
857
858 if ( wc_get_page_id( 'checkout' ) === $post->ID ) {
859 $post_states['wc_page_for_checkout'] = __( 'Checkout Page', 'woocommerce' );
860 }
861
862 if ( wc_get_page_id( 'myaccount' ) === $post->ID ) {
863 $post_states['wc_page_for_myaccount'] = __( 'My Account Page', 'woocommerce' );
864 }
865
866 if ( wc_get_page_id( 'terms' ) === $post->ID ) {
867 $post_states['wc_page_for_terms'] = __( 'Terms and Conditions Page', 'woocommerce' );
868 }
869
870 return $post_states;
871 }
872
873 /**
874 * Apply product type constraints to stock status.
875 *
876 * @param WC_Product $product The product whose stock status will be adjusted.
877 * @param string|null $stock_status The stock status to use for adjustment, or null if no new stock status has been supplied in the request.
878 * @return WC_Product The supplied product, or the synced product if it was a variable product.
879 */
880 private function maybe_update_stock_status( $product, $stock_status ) {
881 if ( $product->is_type( ProductType::EXTERNAL ) ) {
882 // External products are always in stock.
883 $product->set_stock_status( ProductStockStatus::IN_STOCK );
884 } elseif ( isset( $stock_status ) ) {
885 if ( $product->is_type( ProductType::VARIABLE ) && ! $product->get_manage_stock() ) {
886 // Stock status is determined by children.
887 foreach ( $product->get_children() as $child_id ) {
888 $child = wc_get_product( $child_id );
889 if ( ! $product->get_manage_stock() ) {
890 $child->set_stock_status( $stock_status );
891 $child->save();
892 }
893 }
894 $product = WC_Product_Variable::sync( $product, false );
895 } else {
896 $product->set_stock_status( $stock_status );
897 }
898 }
899
900 return $product;
901 }
902
903 /**
904 * Set the new regular or sale price if requested.
905 *
906 * @param WC_Product $product The product to set the new price for.
907 * @param string $price_type 'regular' or 'sale'.
908 * @return bool true if a new price has been set, false otherwise.
909 */
910 private function set_new_price( $product, $price_type ) {
911 // phpcs:disable WordPress.Security.NonceVerification.Recommended
912
913 $request_data = $this->request_data();
914
915 if ( empty( $request_data[ "change_{$price_type}_price" ] ) || ! isset( $request_data[ "_{$price_type}_price" ] ) ) {
916 return false;
917 }
918
919 $old_price = $product->{"get_{$price_type}_price"}();
920 $old_price = '' === $old_price ? (float) $product->get_regular_price() : (float) $old_price;
921 $price_changed = false;
922
923 $change_price = absint( $request_data[ "change_{$price_type}_price" ] );
924 $raw_price = wc_clean( wp_unslash( $request_data[ "_{$price_type}_price" ] ) );
925 $is_percentage = (bool) strstr( $raw_price, '%' );
926 $price = wc_format_decimal( $raw_price );
927
928 switch ( $change_price ) {
929 case 1:
930 if ( empty( $price ) ) {
931 $new_price = $product->get_regular_price();
932 } else {
933 $new_price = $price;
934 }
935 break;
936 case 2:
937 if ( $is_percentage ) {
938 $percent = $price / 100;
939 $new_price = $old_price + ( $old_price * $percent );
940 } elseif ( ! empty( $price ) ) {
941 $new_price = $old_price + $price;
942 }
943 break;
944 case 3:
945 if ( $is_percentage ) {
946 $percent = $price / 100;
947 $new_price = max( 0, $old_price - ( $old_price * $percent ) );
948 } elseif ( ! empty( $price ) ) {
949 $new_price = max( 0, $old_price - $price );
950 }
951 break;
952 case 4:
953 if ( 'sale' !== $price_type ) {
954 break;
955 }
956 $regular_price = $product->get_regular_price();
957 if ( $is_percentage && is_numeric( $regular_price ) ) {
958 $percent = $price / 100;
959 $new_price = max( 0, $regular_price - ( NumberUtil::round( $regular_price * $percent, wc_get_price_decimals() ) ) );
960 } else {
961 $new_price = max( 0, (float) $regular_price - (float) $price );
962 }
963 break;
964
965 default:
966 break;
967 }
968
969 if ( isset( $new_price ) && $new_price !== $old_price ) {
970 $price_changed = true;
971 $new_price = NumberUtil::round( $new_price, wc_get_price_decimals() );
972 $product->{"set_{$price_type}_price"}( $new_price );
973 }
974
975 return $price_changed;
976
977 // phpcs:disable WordPress.Security.NonceVerification.Recommended
978 }
979
980 /**
981 * Get the current request data ($_REQUEST superglobal).
982 * This method is added to ease unit testing.
983 *
984 * @return array The $_REQUEST superglobal.
985 */
986 protected function request_data() {
987 return $_REQUEST;
988 }
989
990 /**
991 * Update the Cost of Goods Sold value coming from a bulk edit for a product.
992 *
993 * @param WC_Product $product The product to update.
994 * @param array $request_data The current request data.
995 */
996 private function maybe_update_cogs_value( WC_Product $product, array $request_data ) {
997 $change_cogs_value = absint( $request_data['change_cogs_value'] );
998 if ( 1 !== $change_cogs_value ) {
999 return;
1000 }
1001
1002 $cogs_value = wc_clean( wp_unslash( $request_data['_cogs_value'] ?? '' ) );
1003 $product->set_cogs_value( '' === $cogs_value ? null : (float) wc_format_decimal( $cogs_value ) );
1004 }
1005 }
1006
1007 new WC_Admin_Post_Types();
1008