PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 11.1.0-beta.2
WooCommerce v11.1.0-beta.2
11.1.0 11.1.0-rc.2 11.1.0-rc.1 11.1.0-beta.2 11.1.0-beta.1 11.0.1 11.0.0 11.0.0-rc.3 11.0.0-rc.2 11.0.0-rc.1 11.0.0-beta.2 11.0.0-beta.1 10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.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 2 weeks ago importers 2 weeks ago list-tables 1 week ago marketplace-suggestions 1 year ago meta-boxes 1 week ago notes 3 months ago plugin-updates 2 years ago reports 2 weeks ago settings 2 weeks ago views 2 weeks ago class-wc-admin-addons.php 10 months ago class-wc-admin-api-keys-table-list.php 3 years ago class-wc-admin-api-keys.php 1 month ago class-wc-admin-assets.php 2 weeks ago class-wc-admin-attributes.php 2 weeks ago class-wc-admin-brands.php 2 weeks ago class-wc-admin-customize.php 6 years ago class-wc-admin-dashboard-setup.php 2 weeks ago class-wc-admin-dashboard.php 2 weeks ago class-wc-admin-duplicate-product.php 2 weeks ago class-wc-admin-exporters.php 1 year ago class-wc-admin-help.php 2 years ago class-wc-admin-importers.php 1 year ago class-wc-admin-log-table-list.php 6 months ago class-wc-admin-marketplace-promotions.php 1 month ago class-wc-admin-menus.php 2 weeks ago class-wc-admin-meta-boxes.php 2 months ago class-wc-admin-notices.php 2 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 2 weeks ago class-wc-admin-profile.php 1 year ago class-wc-admin-reports.php 6 months ago class-wc-admin-settings.php 2 weeks ago class-wc-admin-setup-wizard.php 2 months ago class-wc-admin-status.php 1 year ago class-wc-admin-taxonomies.php 2 months ago class-wc-admin-upload-downloadable-product.php 2 years ago class-wc-admin-webhooks-table-list.php 2 months ago class-wc-admin-webhooks.php 1 month ago class-wc-admin.php 2 weeks ago wc-admin-functions.php 2 weeks ago wc-meta-box-functions.php 1 year ago woocommerce-legacy-reports.php 1 year ago
class-wc-admin-post-types.php
1038 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 use Automattic\WooCommerce\Utilities\TimeUtil;
15
16 if ( ! defined( 'ABSPATH' ) ) {
17 exit;
18 }
19
20 if ( class_exists( 'WC_Admin_Post_Types', false ) ) {
21 new WC_Admin_Post_Types();
22 return;
23 }
24
25 /**
26 * WC_Admin_Post_Types Class.
27 *
28 * Handles the edit posts views and some functionality on the edit post screen for WC post types.
29 */
30 class WC_Admin_Post_Types {
31
32 /**
33 * Constructor.
34 */
35 public function __construct() {
36 include_once __DIR__ . '/class-wc-admin-meta-boxes.php';
37
38 if ( ! function_exists( 'duplicate_post_plugin_activation' ) ) {
39 include_once __DIR__ . '/class-wc-admin-duplicate-product.php';
40 }
41
42 // Load correct list table classes for current screen.
43 add_action( 'current_screen', array( $this, 'setup_screen' ) );
44 add_action( 'check_ajax_referer', array( $this, 'setup_screen' ) );
45
46 // Admin notices.
47 add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) );
48 add_filter( 'woocommerce_order_updated_messages', array( $this, 'order_updated_messages' ) );
49 add_filter( 'bulk_post_updated_messages', array( $this, 'bulk_post_updated_messages' ), 10, 2 );
50 add_action(
51 'admin_notices',
52 function () {
53 $this->maybe_display_warning_for_password_protected_coupon();
54 }
55 );
56
57 // Disable Auto Save.
58 add_action( 'admin_print_scripts', array( $this, 'disable_autosave' ) );
59
60 // Extra post data and screen elements.
61 add_action( 'edit_form_top', array( $this, 'edit_form_top' ) );
62 add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 );
63 add_action( 'edit_form_after_title', array( $this, 'edit_form_after_title' ) );
64 add_filter( 'default_hidden_meta_boxes', array( $this, 'hidden_meta_boxes' ), 10, 2 );
65 add_action( 'post_submitbox_misc_actions', array( $this, 'product_data_visibility' ), 5 );
66
67 include_once __DIR__ . '/class-wc-admin-upload-downloadable-product.php';
68
69 // Hide template for CPT archive.
70 add_filter( 'theme_page_templates', array( $this, 'hide_cpt_archive_templates' ), 10, 3 );
71 add_action( 'edit_form_top', array( $this, 'show_cpt_archive_notice' ) );
72
73 // Add a post display state for special WC pages.
74 add_filter( 'display_post_states', array( $this, 'add_display_post_states' ), 10, 2 );
75
76 // Bulk / quick edit.
77 add_action( 'bulk_edit_custom_box', array( $this, 'bulk_edit' ), 10, 2 );
78 add_action( 'quick_edit_custom_box', array( $this, 'quick_edit' ), 10, 2 );
79 add_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ), 10, 2 );
80 add_action( 'woocommerce_product_bulk_and_quick_edit', array( $this, 'bulk_and_quick_edit_save_post' ), 10, 2 );
81 }
82
83 /**
84 * Looks at the current screen and loads the correct list table handler.
85 *
86 * @since 3.3.0
87 */
88 public function setup_screen() {
89 global $wc_list_table;
90
91 $request_data = $this->request_data();
92
93 $screen_id = false;
94
95 if ( function_exists( 'get_current_screen' ) ) {
96 $screen = get_current_screen();
97 $screen_id = isset( $screen, $screen->id ) ? $screen->id : '';
98 }
99
100 if ( ! empty( $request_data['screen'] ) ) {
101 $screen_id = wc_clean( wp_unslash( $request_data['screen'] ) );
102 }
103
104 switch ( $screen_id ) {
105 case 'edit-shop_order':
106 include_once __DIR__ . '/list-tables/class-wc-admin-list-table-orders.php';
107 $wc_list_table = new WC_Admin_List_Table_Orders();
108 break;
109 case 'edit-shop_coupon':
110 include_once __DIR__ . '/list-tables/class-wc-admin-list-table-coupons.php';
111 $wc_list_table = new WC_Admin_List_Table_Coupons();
112 break;
113 case 'edit-product':
114 include_once __DIR__ . '/list-tables/class-wc-admin-list-table-products.php';
115 $wc_list_table = new WC_Admin_List_Table_Products();
116 break;
117 }
118
119 // Ensure the table handler is only loaded once. Prevents multiple loads if a plugin calls check_ajax_referer many times.
120 remove_action( 'current_screen', array( $this, 'setup_screen' ) );
121 remove_action( 'check_ajax_referer', array( $this, 'setup_screen' ) );
122 }
123
124 /**
125 * Change messages when a post type is updated.
126 *
127 * @param array $messages Array of messages.
128 * @return array
129 */
130 public function post_updated_messages( $messages ) {
131 global $post;
132
133 $messages['product'] = array(
134 0 => '', // Unused. Messages start at index 1.
135 /* translators: %1$s: Product link opening tag. %2$s: Product link closing tag.*/
136 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>' ),
137 2 => __( 'Custom field updated.', 'woocommerce' ),
138 3 => __( 'Custom field deleted.', 'woocommerce' ),
139 4 => __( 'Product updated.', 'woocommerce' ),
140 5 => __( 'Revision restored.', 'woocommerce' ),
141 /* translators: %1$s: Product link opening tag. %2$s: Product link closing tag.*/
142 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>' ),
143 7 => __( 'Product saved.', 'woocommerce' ),
144 /* translators: %s: product url */
145 8 => sprintf( __( 'Product submitted. <a target="_blank" href="%s">Preview product</a>', 'woocommerce' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) ),
146 9 => sprintf(
147 /* translators: 1: date 2: product url */
148 __( 'Product scheduled for: %1$s. <a target="_blank" href="%2$s">Preview product</a>', 'woocommerce' ),
149 '<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ) . '</strong>',
150 esc_url( get_permalink( $post->ID ) )
151 ),
152 /* translators: %s: product url */
153 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 ) ) ) ),
154 );
155
156 $messages = $this->order_updated_messages( $messages );
157
158 $messages['shop_coupon'] = array(
159 0 => '', // Unused. Messages start at index 1.
160 1 => __( 'Coupon updated.', 'woocommerce' ),
161 2 => __( 'Custom field updated.', 'woocommerce' ),
162 3 => __( 'Custom field deleted.', 'woocommerce' ),
163 4 => __( 'Coupon updated.', 'woocommerce' ),
164 5 => __( 'Revision restored.', 'woocommerce' ),
165 6 => __( 'Coupon updated.', 'woocommerce' ),
166 7 => __( 'Coupon saved.', 'woocommerce' ),
167 8 => __( 'Coupon submitted.', 'woocommerce' ),
168 9 => sprintf(
169 /* translators: %s: date */
170 __( 'Coupon scheduled for: %s.', 'woocommerce' ),
171 '<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ) . '</strong>'
172 ),
173 10 => __( 'Coupon draft updated.', 'woocommerce' ),
174 );
175
176 return $messages;
177 }
178
179 /**
180 * Add messages when an order is updated.
181 *
182 * @param array $messages Array of messages.
183 *
184 * @return array
185 */
186 public function order_updated_messages( array $messages ) {
187 global $post, $theorder;
188
189 if ( ! isset( $theorder ) || ! $theorder instanceof WC_Abstract_Order ) {
190 if ( ! isset( $post ) || 'shop_order' !== $post->post_type ) {
191 return $messages;
192 } else {
193 \Automattic\WooCommerce\Utilities\OrderUtil::init_theorder_object( $post );
194 }
195 }
196
197 $messages['shop_order'] = array(
198 0 => '', // Unused. Messages start at index 1.
199 1 => __( 'Order updated.', 'woocommerce' ),
200 2 => __( 'Custom field updated.', 'woocommerce' ),
201 3 => __( 'Custom field deleted.', 'woocommerce' ),
202 4 => __( 'Order updated.', 'woocommerce' ),
203 5 => __( 'Revision restored.', 'woocommerce' ),
204 6 => __( 'Order updated.', 'woocommerce' ),
205 7 => __( 'Order saved.', 'woocommerce' ),
206 8 => __( 'Order submitted.', 'woocommerce' ),
207 9 => sprintf(
208 /* translators: %s: date */
209 __( 'Order scheduled for: %s.', 'woocommerce' ),
210 '<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $theorder->get_date_created() ?? $post->post_date ) ) . '</strong>'
211 ),
212 10 => __( 'Order draft updated.', 'woocommerce' ),
213 11 => __( 'Order updated and sent.', 'woocommerce' ),
214 );
215
216 return $messages;
217 }
218
219 /**
220 * Specify custom bulk actions messages for different post types.
221 *
222 * @param array $bulk_messages Array of messages.
223 * @param array $bulk_counts Array of how many objects were updated.
224 * @return array
225 */
226 public function bulk_post_updated_messages( $bulk_messages, $bulk_counts ) {
227 $bulk_messages['product'] = array(
228 /* translators: %s: product count */
229 'updated' => _n( '%s product updated.', '%s products updated.', $bulk_counts['updated'], 'woocommerce' ),
230 /* translators: %s: product count */
231 'locked' => _n( '%s product not updated, somebody is editing it.', '%s products not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
232 /* translators: %s: product count */
233 'deleted' => _n( '%s product permanently deleted.', '%s products permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
234 /* translators: %s: product count */
235 'trashed' => _n( '%s product moved to the Trash.', '%s products moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
236 /* translators: %s: product count */
237 'untrashed' => _n( '%s product restored from the Trash.', '%s products restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
238 );
239
240 $bulk_messages['shop_order'] = array(
241 /* translators: %s: order count */
242 'updated' => _n( '%s order updated.', '%s orders updated.', $bulk_counts['updated'], 'woocommerce' ),
243 /* translators: %s: order count */
244 'locked' => _n( '%s order not updated, somebody is editing it.', '%s orders not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
245 /* translators: %s: order count */
246 'deleted' => _n( '%s order permanently deleted.', '%s orders permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
247 /* translators: %s: order count */
248 'trashed' => _n( '%s order moved to the Trash.', '%s orders moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
249 /* translators: %s: order count */
250 'untrashed' => _n( '%s order restored from the Trash.', '%s orders restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
251 );
252
253 $bulk_messages['shop_coupon'] = array(
254 /* translators: %s: coupon count */
255 'updated' => _n( '%s coupon updated.', '%s coupons updated.', $bulk_counts['updated'], 'woocommerce' ),
256 /* translators: %s: coupon count */
257 'locked' => _n( '%s coupon not updated, somebody is editing it.', '%s coupons not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
258 /* translators: %s: coupon count */
259 'deleted' => _n( '%s coupon permanently deleted.', '%s coupons permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
260 /* translators: %s: coupon count */
261 'trashed' => _n( '%s coupon moved to the Trash.', '%s coupons moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
262 /* translators: %s: coupon count */
263 'untrashed' => _n( '%s coupon restored from the Trash.', '%s coupons restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
264 );
265
266 return $bulk_messages;
267 }
268
269 /**
270 * Shows a warning when editing a password-protected coupon.
271 *
272 * @since 9.2.0
273 */
274 private function maybe_display_warning_for_password_protected_coupon() {
275 if ( ! function_exists( 'get_current_screen' ) || 'shop_coupon' !== get_current_screen()->id ) {
276 return;
277 }
278
279 if ( ! isset( $GLOBALS['post'] ) || 'shop_coupon' !== $GLOBALS['post']->post_type ) {
280 return;
281 }
282
283 wp_admin_notice(
284 __(
285 '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.',
286 'woocommerce'
287 ),
288 array(
289 'type' => 'warning',
290 'id' => 'wc-password-protected-coupon-warning',
291 'additional_classes' => empty( $GLOBALS['post']->post_password ) ? array( 'hidden' ) : array(),
292 )
293 );
294 }
295
296 /**
297 * Custom bulk edit - form.
298 *
299 * @param string $column_name Column being shown.
300 * @param string $post_type Post type being shown.
301 */
302 public function bulk_edit( $column_name, $post_type ) {
303 if ( 'price' !== $column_name || 'product' !== $post_type ) {
304 return;
305 }
306
307 $shipping_class = get_terms(
308 'product_shipping_class',
309 array(
310 'hide_empty' => false,
311 )
312 );
313
314 include WC()->plugin_path() . '/includes/admin/views/html-bulk-edit-product.php';
315 }
316
317 /**
318 * Custom quick edit - form.
319 *
320 * @param string $column_name Column being shown.
321 * @param string $post_type Post type being shown.
322 */
323 public function quick_edit( $column_name, $post_type ) {
324 if ( 'price' !== $column_name || 'product' !== $post_type ) {
325 return;
326 }
327
328 $shipping_class = get_terms(
329 'product_shipping_class',
330 array(
331 'hide_empty' => false,
332 )
333 );
334
335 include WC()->plugin_path() . '/includes/admin/views/html-quick-edit-product.php';
336 }
337
338 /**
339 * Offers a way to hook into save post without causing an infinite loop
340 * when quick/bulk saving product info.
341 *
342 * @since 3.0.0
343 * @param int $post_id Post ID being saved.
344 * @param object $post Post object being saved.
345 */
346 public function bulk_and_quick_edit_hook( $post_id, $post ) {
347 remove_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ) );
348 do_action( 'woocommerce_product_bulk_and_quick_edit', $post_id, $post );
349 add_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ), 10, 2 );
350 }
351
352 /**
353 * Quick and bulk edit saving.
354 *
355 * @param int $post_id Post ID being saved.
356 * @param object $post Post object being saved.
357 * @return int
358 */
359 public function bulk_and_quick_edit_save_post( $post_id, $post ) {
360 $request_data = $this->request_data();
361
362 // If this is an autosave, our form has not been submitted, so we don't want to do anything.
363 if ( Constants::is_true( 'DOING_AUTOSAVE' ) ) {
364 return $post_id;
365 }
366
367 // Don't save revisions and autosaves.
368 if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) || 'product' !== $post->post_type || ! current_user_can( 'edit_post', $post_id ) ) {
369 return $post_id;
370 }
371
372 // Check nonce.
373 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash
374 if ( ! isset( $request_data['woocommerce_quick_edit_nonce'] ) || ! wp_verify_nonce( $request_data['woocommerce_quick_edit_nonce'], 'woocommerce_quick_edit_nonce' ) ) {
375 return $post_id;
376 }
377
378 // Get the product and save.
379 $product = wc_get_product( $post );
380
381 if ( ! empty( $request_data['woocommerce_quick_edit'] ) ) { // WPCS: input var ok.
382 $this->quick_edit_save( $post_id, $product );
383 } else {
384 $this->bulk_edit_save( $post_id, $product );
385 }
386
387 return $post_id;
388 }
389
390 /**
391 * Quick edit.
392 *
393 * @param int $post_id Post ID being saved.
394 * @param WC_Product $product Product object.
395 */
396 private function quick_edit_save( $post_id, $product ) {
397 $request_data = $this->request_data();
398
399 $data_store = $product->get_data_store();
400 $input_to_props = array(
401 '_weight' => 'weight',
402 '_length' => 'length',
403 '_width' => 'width',
404 '_height' => 'height',
405 '_visibility' => 'catalog_visibility',
406 '_tax_class' => 'tax_class',
407 '_tax_status' => 'tax_status',
408 );
409
410 foreach ( $input_to_props as $input_var => $prop ) {
411 if ( isset( $request_data[ $input_var ] ) ) {
412 $product->{"set_{$prop}"}( wc_clean( wp_unslash( $request_data[ $input_var ] ) ) );
413 }
414 }
415
416 if ( isset( $request_data['_sku'] ) ) {
417 $sku = $product->get_sku();
418 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
419 $new_sku = (string) wc_clean( $request_data['_sku'] );
420
421 if ( $new_sku !== $sku ) {
422 if ( ! empty( $new_sku ) ) {
423 $unique_sku = wc_product_has_unique_sku( $post_id, $new_sku );
424 if ( $unique_sku ) {
425 $product->set_sku( wc_clean( wp_unslash( $new_sku ) ) );
426 }
427 } else {
428 $product->set_sku( '' );
429 }
430 }
431 }
432
433 if ( ! empty( $request_data['_shipping_class'] ) ) {
434 if ( '_no_shipping_class' === $request_data['_shipping_class'] ) {
435 $product->set_shipping_class_id( 0 );
436 } else {
437 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
438 $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $request_data['_shipping_class'] ) );
439 $product->set_shipping_class_id( $shipping_class_id );
440 }
441 }
442
443 if ( ! empty( $request_data['_tax_class'] ) ) {
444 $tax_class = sanitize_title( wp_unslash( $request_data['_tax_class'] ) );
445 if ( 'standard' === $tax_class ) {
446 $tax_class = '';
447 }
448 $product->set_tax_class( $tax_class );
449 }
450
451 $product->set_featured( isset( $request_data['_featured'] ) );
452
453 if ( $product->is_type( ProductType::SIMPLE ) || $product->is_type( ProductType::EXTERNAL ) ) {
454
455 if ( isset( $request_data['_regular_price'] ) ) {
456 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
457 $regular_price = ( '' === $request_data['_regular_price'] ) ? '' : wc_format_decimal( $request_data['_regular_price'] );
458 $product->set_regular_price( $regular_price );
459 }
460
461 if ( isset( $request_data['_sale_price'] ) ) {
462 // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash
463 $sale_price = ( '' === $request_data['_sale_price'] ) ? '' : wc_format_decimal( $request_data['_sale_price'] );
464 $product->set_sale_price( $sale_price );
465 }
466
467 // Parse valid dates using the full product editor's site-timezone behavior.
468 $submitted_sale_date_from = $request_data['_sale_price_dates_from'] ?? null;
469 if ( is_string( $submitted_sale_date_from ) ) {
470 /**
471 * Submitted sale start date.
472 *
473 * @var string $date_on_sale_from
474 */
475 $date_on_sale_from = wp_unslash( $submitted_sale_date_from );
476
477 if ( '' === $date_on_sale_from ) {
478 $product->set_date_on_sale_from( '' );
479 } elseif ( TimeUtil::is_valid_date( $date_on_sale_from, 'Y-m-d' ) ) {
480 $timestamp = strtotime( $date_on_sale_from );
481 if ( false !== $timestamp ) {
482 $product->set_date_on_sale_from( date( 'Y-m-d 00:00:00', $timestamp ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
483 }
484 }
485 }
486
487 $submitted_sale_date_to = $request_data['_sale_price_dates_to'] ?? null;
488 if ( is_string( $submitted_sale_date_to ) ) {
489 /**
490 * Submitted sale end date.
491 *
492 * @var string $date_on_sale_to
493 */
494 $date_on_sale_to = wp_unslash( $submitted_sale_date_to );
495
496 if ( '' === $date_on_sale_to ) {
497 $product->set_date_on_sale_to( '' );
498 } elseif ( TimeUtil::is_valid_date( $date_on_sale_to, 'Y-m-d' ) ) {
499 $timestamp = strtotime( $date_on_sale_to );
500 if ( false !== $timestamp ) {
501 $product->set_date_on_sale_to( date( 'Y-m-d 23:59:59', $timestamp ) ); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
502 }
503 }
504 }
505 }
506
507 if ( wc_get_container()->get( CostOfGoodsSoldController::class )->feature_is_enabled() && isset( $request_data['_cogs_value'] ) ) {
508 $cogs_value = $request_data['_cogs_value'];
509 $cogs_value = '' === $cogs_value ? null : (float) wc_format_decimal( $cogs_value );
510 $product->set_cogs_value( $cogs_value );
511 }
512
513 // Handle Stock Data.
514 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
515 $manage_stock = ! empty( $request_data['_manage_stock'] ) && ProductType::GROUPED !== $product->get_type() ? 'yes' : 'no';
516 $backorders = ! empty( $request_data['_backorders'] ) ? wc_clean( $request_data['_backorders'] ) : 'no';
517 if ( ! empty( $request_data['_stock_status'] ) ) {
518 $stock_status = wc_clean( $request_data['_stock_status'] );
519 } else {
520 $stock_status = $product->is_type( ProductType::VARIABLE ) ? null : ProductStockStatus::IN_STOCK;
521 }
522 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
523
524 $product->set_manage_stock( $manage_stock );
525
526 if ( ProductType::EXTERNAL !== $product->get_type() ) {
527 $product->set_backorders( $backorders );
528 }
529
530 if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
531 if ( 'yes' === $manage_stock && isset( $request_data['_stock'] ) ) {
532 $stock_value = wp_unslash( $request_data['_stock'] );
533 $stock_amount = is_numeric( $stock_value ) ? wc_stock_amount( $stock_value ) : 0;
534 } else {
535 $stock_amount = '';
536 }
537 $product->set_stock_quantity( $stock_amount );
538 }
539
540 $product = $this->maybe_update_stock_status( $product, $stock_status );
541
542 $product->save();
543
544 do_action( 'woocommerce_product_quick_edit_save', $product );
545 }
546
547 /**
548 * Bulk edit.
549 *
550 * @param int $post_id Post ID being saved.
551 * @param WC_Product $product Product object.
552 */
553 public function bulk_edit_save( $post_id, $product ) {
554 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
555
556 $request_data = $this->request_data();
557
558 $data_store = $product->get_data_store();
559
560 if ( ! empty( $request_data['change_weight'] ) && isset( $request_data['_weight'] ) ) {
561 $product->set_weight( wc_clean( wp_unslash( $request_data['_weight'] ) ) );
562 }
563
564 if ( ! empty( $request_data['change_dimensions'] ) ) {
565 if ( isset( $request_data['_length'] ) ) {
566 $product->set_length( wc_clean( wp_unslash( $request_data['_length'] ) ) );
567 }
568 if ( isset( $request_data['_width'] ) ) {
569 $product->set_width( wc_clean( wp_unslash( $request_data['_width'] ) ) );
570 }
571 if ( isset( $request_data['_height'] ) ) {
572 $product->set_height( wc_clean( wp_unslash( $request_data['_height'] ) ) );
573 }
574 }
575
576 if ( ! empty( $request_data['_tax_status'] ) ) {
577 $product->set_tax_status( wc_clean( $request_data['_tax_status'] ) );
578 }
579
580 if ( ! empty( $request_data['_tax_class'] ) ) {
581 $tax_class = sanitize_title( wp_unslash( $request_data['_tax_class'] ) );
582 if ( 'standard' === $tax_class ) {
583 $tax_class = '';
584 }
585 $product->set_tax_class( $tax_class );
586 }
587
588 if ( ! empty( $request_data['_shipping_class'] ) ) {
589 if ( '_no_shipping_class' === $request_data['_shipping_class'] ) {
590 $product->set_shipping_class_id( 0 );
591 } else {
592 $shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $request_data['_shipping_class'] ) );
593 $product->set_shipping_class_id( $shipping_class_id );
594 }
595 }
596
597 if ( ! empty( $request_data['_visibility'] ) ) {
598 $product->set_catalog_visibility( wc_clean( $request_data['_visibility'] ) );
599 }
600
601 if ( ! empty( $request_data['_featured'] ) ) {
602 // phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
603 $product->set_featured( wp_unslash( $request_data['_featured'] ) );
604 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
605 }
606
607 if ( ! empty( $request_data['_sold_individually'] ) ) {
608 if ( 'yes' === $request_data['_sold_individually'] ) {
609 $product->set_sold_individually( 'yes' );
610 } else {
611 $product->set_sold_individually( '' );
612 }
613 }
614
615 /**
616 * Handle price - remove dates and set to lowest.
617 *
618 * @param array $product_types Array of product types that can change price.
619 *
620 * @since 3.0.0
621 */
622 $change_price_product_types = apply_filters( 'woocommerce_bulk_edit_save_price_product_types', array( ProductType::SIMPLE, ProductType::EXTERNAL ) );
623 $can_product_type_change_price = false;
624 foreach ( $change_price_product_types as $product_type ) {
625 if ( $product->is_type( $product_type ) ) {
626 $can_product_type_change_price = true;
627 break;
628 }
629 }
630
631 if ( $can_product_type_change_price ) {
632 $regular_price_changed = $this->set_new_price( $product, 'regular' );
633 $sale_price_changed = $this->set_new_price( $product, 'sale' );
634
635 if ( $regular_price_changed || $sale_price_changed ) {
636 $product->set_date_on_sale_to( '' );
637 $product->set_date_on_sale_from( '' );
638
639 if ( $product->get_regular_price() < $product->get_sale_price() ) {
640 $product->set_sale_price( '' );
641 }
642 }
643 }
644
645 // Handle Stock Data.
646 $was_managing_stock = $product->get_manage_stock() ? 'yes' : 'no';
647 $backorders = $product->get_backorders();
648 $backorders = ! empty( $request_data['_backorders'] ) ? wc_clean( $request_data['_backorders'] ) : $backorders;
649
650 if ( ! empty( $request_data['_manage_stock'] ) ) {
651 $manage_stock = 'yes' === wc_clean( $request_data['_manage_stock'] ) && ProductType::GROUPED !== $product->get_type() ? 'yes' : 'no';
652 } else {
653 $manage_stock = $was_managing_stock;
654 }
655
656 $stock_amount = 'yes' === $manage_stock && ! empty( $request_data['change_stock'] ) && isset( $request_data['_stock'] ) ? wc_stock_amount( $request_data['_stock'] ) : $product->get_stock_quantity();
657
658 $product->set_manage_stock( $manage_stock );
659
660 if ( ProductType::EXTERNAL !== $product->get_type() ) {
661 $product->set_backorders( $backorders );
662 }
663
664 if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
665 $change_stock = absint( $request_data['change_stock'] );
666 switch ( $change_stock ) {
667 case 2:
668 wc_update_product_stock( $product, $stock_amount, 'increase', true );
669 break;
670 case 3:
671 wc_update_product_stock( $product, $stock_amount, 'decrease', true );
672 break;
673 default:
674 wc_update_product_stock( $product, $stock_amount, 'set', true );
675 break;
676 }
677 } else {
678 // Reset values if WooCommerce Setting - Manage Stock status is disabled.
679 $product->set_stock_quantity( '' );
680 $product->set_manage_stock( 'no' );
681 }
682
683 $stock_status = empty( $request_data['_stock_status'] ) ? null : wc_clean( $request_data['_stock_status'] );
684 $product = $this->maybe_update_stock_status( $product, $stock_status );
685
686 if ( wc_get_container()->get( CostOfGoodsSoldController::class )->feature_is_enabled() ) {
687 $this->maybe_update_cogs_value( $product, $request_data );
688 }
689
690 $product->save();
691
692 do_action( 'woocommerce_product_bulk_edit_save', $product );
693
694 // phpcs:enable WordPress.Security.ValidatedSanitizedInput.MissingUnslash
695 }
696
697 /**
698 * Disable the auto-save functionality for Orders.
699 */
700 public function disable_autosave() {
701 global $post;
702
703 if ( $post instanceof WP_Post && in_array( get_post_type( $post->ID ), wc_get_order_types( 'order-meta-boxes' ), true ) ) {
704 wp_dequeue_script( 'autosave' );
705 }
706 }
707
708 /**
709 * Output extra data on post forms.
710 *
711 * @param WP_Post $post Current post object.
712 */
713 public function edit_form_top( $post ) {
714 echo '<input type="hidden" id="original_post_title" name="original_post_title" value="' . esc_attr( $post->post_title ) . '" />';
715 }
716
717 /**
718 * Change title boxes in admin.
719 *
720 * @param string $text Text to shown.
721 * @param WP_Post $post Current post object.
722 * @return string
723 */
724 public function enter_title_here( $text, $post ) {
725 switch ( $post->post_type ) {
726 case 'product':
727 $text = esc_html__( 'Product name', 'woocommerce' );
728 break;
729 case 'shop_coupon':
730 $text = esc_html__( 'Coupon code', 'woocommerce' );
731 break;
732 }
733 return $text;
734 }
735
736 /**
737 * Print coupon description textarea field.
738 *
739 * @param WP_Post $post Current post object.
740 */
741 public function edit_form_after_title( $post ) {
742 // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped
743 if ( 'shop_coupon' === $post->post_type ) {
744 ?>
745 <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>
746 <?php
747 }
748 // phpcs:enable WordPress.Security.EscapeOutput.OutputNotEscaped
749 }
750
751 /**
752 * Hidden default Meta-Boxes.
753 *
754 * @param array $hidden Hidden boxes.
755 * @param WP_Screen $screen Current screen.
756 * @return array
757 */
758 public function hidden_meta_boxes( $hidden, $screen ) {
759 if ( 'product' === $screen->post_type && 'post' === $screen->base ) {
760 $hidden = array_merge( $hidden, array( 'postcustom' ) );
761 }
762
763 // Download permissions are granted automatically, so hide the box by default on order screens (HPOS and legacy CPT). Merchants can re-enable it via Screen Options.
764 if ( wc_get_page_screen_id( 'shop-order' ) === $screen->id ) {
765 $hidden = array_merge( $hidden, array( 'woocommerce-order-downloads' ) );
766 }
767
768 return $hidden;
769 }
770
771 /**
772 * Output product visibility options.
773 */
774 public function product_data_visibility() {
775 global $post, $thepostid, $product_object;
776
777 if ( 'product' !== $post->post_type ) {
778 return;
779 }
780
781 $thepostid = $post->ID;
782 $product_object = $thepostid ? wc_get_product( $thepostid ) : new WC_Product();
783 $current_visibility = $product_object->get_catalog_visibility();
784 $current_featured = wc_bool_to_string( $product_object->get_featured() );
785 $visibility_options = wc_get_product_visibility_options();
786 ?>
787 <div class="misc-pub-section misc-pub-catalog-visibility" id="catalog-visibility">
788 <?php esc_html_e( 'Catalog visibility:', 'woocommerce' ); ?>
789 <strong id="catalog-visibility-display">
790 <?php
791
792 echo isset( $visibility_options[ $current_visibility ] ) ? esc_html( $visibility_options[ $current_visibility ] ) : esc_html( $current_visibility );
793
794 if ( 'yes' === $current_featured ) {
795 echo ', ' . esc_html__( 'Featured', 'woocommerce' );
796 }
797 ?>
798 </strong>
799
800 <a href="#catalog-visibility" class="edit-catalog-visibility hide-if-no-js"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a>
801
802 <div id="catalog-visibility-select" class="hide-if-js">
803
804 <input type="hidden" name="current_visibility" id="current_visibility" value="<?php echo esc_attr( $current_visibility ); ?>" />
805 <input type="hidden" name="current_featured" id="current_featured" value="<?php echo esc_attr( $current_featured ); ?>" />
806
807 <?php
808 echo '<p>' . esc_html__( 'This setting determines which shop pages products will be listed on.', 'woocommerce' ) . '</p>';
809
810 foreach ( $visibility_options as $name => $label ) {
811 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 />';
812 }
813
814 echo '<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 />';
815 ?>
816 <p>
817 <a href="#catalog-visibility" class="save-post-visibility hide-if-no-js button"><?php esc_html_e( 'OK', 'woocommerce' ); ?></a>
818 <a href="#catalog-visibility" class="cancel-post-visibility hide-if-no-js"><?php esc_html_e( 'Cancel', 'woocommerce' ); ?></a>
819 </p>
820 </div>
821 </div>
822 <?php
823 }
824
825 /**
826 * Grant downloadable file access to any newly added files on any existing.
827 * orders for this product that have previously been granted downloadable file access.
828 *
829 * @param int $product_id product identifier.
830 * @param int $variation_id optional product variation identifier.
831 * @param array $downloadable_files newly set files.
832 * @deprecated 3.3.0 and moved to post-data class.
833 */
834 public function process_product_file_download_paths( $product_id, $variation_id, $downloadable_files ) {
835 wc_deprecated_function( 'WC_Admin_Post_Types::process_product_file_download_paths', '3.3', '' );
836 WC_Post_Data::process_product_file_download_paths( $product_id, $variation_id, $downloadable_files );
837 }
838
839 /**
840 * When editing the shop page, we should hide templates.
841 *
842 * @param array $page_templates Templates array.
843 * @param string $theme Classname.
844 * @param WP_Post $post The current post object.
845 * @return array
846 */
847 public function hide_cpt_archive_templates( $page_templates, $theme, $post ) {
848 $shop_page_id = wc_get_page_id( 'shop' );
849
850 if ( $post && absint( $post->ID ) === $shop_page_id ) {
851 $page_templates = array();
852 }
853
854 return $page_templates;
855 }
856
857 /**
858 * Show a notice above the CPT archive.
859 *
860 * @param WP_Post $post The current post object.
861 */
862 public function show_cpt_archive_notice( $post ) {
863 $shop_page_id = wc_get_page_id( 'shop' );
864
865 if ( $post && absint( $post->ID ) === $shop_page_id ) {
866 echo '<div class="notice notice-info">';
867 /* translators: %s: URL to read more about the shop page. */
868 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>';
869 echo '</div>';
870 }
871 }
872
873 /**
874 * Add a post display state for special WC pages in the page list table.
875 *
876 * @param array $post_states An array of post display states.
877 * @param WP_Post $post The current post object.
878 */
879 public function add_display_post_states( $post_states, $post ) {
880 if ( wc_get_page_id( 'shop' ) === $post->ID ) {
881 $post_states['wc_page_for_shop'] = __( 'Shop Page', 'woocommerce' );
882 }
883
884 if ( wc_get_page_id( 'cart' ) === $post->ID ) {
885 $post_states['wc_page_for_cart'] = __( 'Cart Page', 'woocommerce' );
886 }
887
888 if ( wc_get_page_id( 'checkout' ) === $post->ID ) {
889 $post_states['wc_page_for_checkout'] = __( 'Checkout Page', 'woocommerce' );
890 }
891
892 if ( wc_get_page_id( 'myaccount' ) === $post->ID ) {
893 $post_states['wc_page_for_myaccount'] = __( 'My Account Page', 'woocommerce' );
894 }
895
896 if ( wc_get_page_id( 'terms' ) === $post->ID ) {
897 $post_states['wc_page_for_terms'] = __( 'Terms and Conditions Page', 'woocommerce' );
898 }
899
900 return $post_states;
901 }
902
903 /**
904 * Apply product type constraints to stock status.
905 *
906 * @param WC_Product $product The product whose stock status will be adjusted.
907 * @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.
908 * @return WC_Product The supplied product, or the synced product if it was a variable product.
909 */
910 private function maybe_update_stock_status( $product, $stock_status ) {
911 if ( $product->is_type( ProductType::EXTERNAL ) ) {
912 // External products are always in stock.
913 $product->set_stock_status( ProductStockStatus::IN_STOCK );
914 } elseif ( isset( $stock_status ) ) {
915 if ( $product->is_type( ProductType::VARIABLE ) && ! $product->get_manage_stock() ) {
916 // Stock status is determined by children.
917 foreach ( $product->get_children() as $child_id ) {
918 $child = wc_get_product( $child_id );
919 if ( ! $product->get_manage_stock() ) {
920 $child->set_stock_status( $stock_status );
921 $child->save();
922 }
923 }
924 $product = WC_Product_Variable::sync( $product, false );
925 } else {
926 $product->set_stock_status( $stock_status );
927 }
928 }
929
930 return $product;
931 }
932
933 /**
934 * Set the new regular or sale price if requested.
935 *
936 * @param WC_Product $product The product to set the new price for.
937 * @param string $price_type 'regular' or 'sale'.
938 * @return bool true if a new price has been set, false otherwise.
939 */
940 private function set_new_price( $product, $price_type ) {
941 // phpcs:disable WordPress.Security.NonceVerification.Recommended
942
943 $request_data = $this->request_data();
944
945 if ( empty( $request_data[ "change_{$price_type}_price" ] ) || ! isset( $request_data[ "_{$price_type}_price" ] ) ) {
946 return false;
947 }
948
949 $old_price = $product->{"get_{$price_type}_price"}();
950 $old_price = '' === $old_price ? (float) $product->get_regular_price() : (float) $old_price;
951 $price_changed = false;
952
953 $change_price = absint( $request_data[ "change_{$price_type}_price" ] );
954 $raw_price = wc_clean( wp_unslash( $request_data[ "_{$price_type}_price" ] ) );
955 $is_percentage = (bool) strstr( $raw_price, '%' );
956 $price = wc_format_decimal( $raw_price );
957
958 switch ( $change_price ) {
959 case 1:
960 if ( empty( $price ) ) {
961 $new_price = $product->get_regular_price();
962 } else {
963 $new_price = $price;
964 }
965 break;
966 case 2:
967 if ( $is_percentage ) {
968 $percent = $price / 100;
969 $new_price = $old_price + ( $old_price * $percent );
970 } elseif ( ! empty( $price ) ) {
971 $new_price = $old_price + $price;
972 }
973 break;
974 case 3:
975 if ( $is_percentage ) {
976 $percent = $price / 100;
977 $new_price = max( 0, $old_price - ( $old_price * $percent ) );
978 } elseif ( ! empty( $price ) ) {
979 $new_price = max( 0, $old_price - $price );
980 }
981 break;
982 case 4:
983 if ( 'sale' !== $price_type ) {
984 break;
985 }
986 $regular_price = $product->get_regular_price();
987 if ( $is_percentage && is_numeric( $regular_price ) ) {
988 $percent = $price / 100;
989 $new_price = max( 0, $regular_price - ( NumberUtil::round( $regular_price * $percent, wc_get_price_decimals() ) ) );
990 } else {
991 $new_price = max( 0, (float) $regular_price - (float) $price );
992 }
993 break;
994
995 default:
996 break;
997 }
998
999 if ( isset( $new_price ) && $new_price !== $old_price ) {
1000 $price_changed = true;
1001 $new_price = NumberUtil::round( $new_price, wc_get_price_decimals() );
1002 $product->{"set_{$price_type}_price"}( $new_price );
1003 }
1004
1005 return $price_changed;
1006
1007 // phpcs:disable WordPress.Security.NonceVerification.Recommended
1008 }
1009
1010 /**
1011 * Get the current request data ($_REQUEST superglobal).
1012 * This method is added to ease unit testing.
1013 *
1014 * @return array The $_REQUEST superglobal.
1015 */
1016 protected function request_data() {
1017 return $_REQUEST;
1018 }
1019
1020 /**
1021 * Update the Cost of Goods Sold value coming from a bulk edit for a product.
1022 *
1023 * @param WC_Product $product The product to update.
1024 * @param array $request_data The current request data.
1025 */
1026 private function maybe_update_cogs_value( WC_Product $product, array $request_data ) {
1027 $change_cogs_value = absint( $request_data['change_cogs_value'] );
1028 if ( 1 !== $change_cogs_value ) {
1029 return;
1030 }
1031
1032 $cogs_value = wc_clean( wp_unslash( $request_data['_cogs_value'] ?? '' ) );
1033 $product->set_cogs_value( '' === $cogs_value ? null : (float) wc_format_decimal( $cogs_value ) );
1034 }
1035 }
1036
1037 new WC_Admin_Post_Types();
1038