Issues (942)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/admin/class-wc-admin-post-types.php (2 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Post Types Admin
4
 *
5
 * @package  WooCommerce/admin
6
 * @version  3.3.0
7
 */
8
9
if ( ! defined( 'ABSPATH' ) ) {
10
	exit;
11
}
12
13
if ( class_exists( 'WC_Admin_Post_Types', false ) ) {
14
	new WC_Admin_Post_Types();
15
	return;
16
}
17
18
/**
19
 * WC_Admin_Post_Types Class.
20
 *
21
 * Handles the edit posts views and some functionality on the edit post screen for WC post types.
22
 */
23
class WC_Admin_Post_Types {
24
25
	/**
26
	 * Constructor.
27
	 */
28
	public function __construct() {
29
		include_once dirname( __FILE__ ) . '/class-wc-admin-meta-boxes.php';
30
31
		if ( ! function_exists( 'duplicate_post_plugin_activation' ) ) {
32
			include_once 'class-wc-admin-duplicate-product.php';
33
		}
34
35
		// Load correct list table classes for current screen.
36
		add_action( 'current_screen', array( $this, 'setup_screen' ) );
37
		add_action( 'check_ajax_referer', array( $this, 'setup_screen' ) );
38
39
		// Admin notices.
40
		add_filter( 'post_updated_messages', array( $this, 'post_updated_messages' ) );
41
		add_filter( 'bulk_post_updated_messages', array( $this, 'bulk_post_updated_messages' ), 10, 2 );
42
43
		// Disable Auto Save.
44
		add_action( 'admin_print_scripts', array( $this, 'disable_autosave' ) );
45
46
		// Extra post data and screen elements.
47
		add_action( 'edit_form_top', array( $this, 'edit_form_top' ) );
48
		add_filter( 'enter_title_here', array( $this, 'enter_title_here' ), 1, 2 );
49
		add_action( 'edit_form_after_title', array( $this, 'edit_form_after_title' ) );
50
		add_filter( 'default_hidden_meta_boxes', array( $this, 'hidden_meta_boxes' ), 10, 2 );
51
		add_action( 'post_submitbox_misc_actions', array( $this, 'product_data_visibility' ) );
52
53
		// Uploads.
54
		add_filter( 'upload_dir', array( $this, 'upload_dir' ) );
55
		add_action( 'media_upload_downloadable_product', array( $this, 'media_upload_downloadable_product' ) );
56
57
		// Hide template for CPT archive.
58
		add_filter( 'theme_page_templates', array( $this, 'hide_cpt_archive_templates' ), 10, 3 );
59
		add_action( 'edit_form_top', array( $this, 'show_cpt_archive_notice' ) );
60
61
		// Add a post display state for special WC pages.
62
		add_filter( 'display_post_states', array( $this, 'add_display_post_states' ), 10, 2 );
63
64
		// Bulk / quick edit.
65
		add_action( 'bulk_edit_custom_box', array( $this, 'bulk_edit' ), 10, 2 );
66
		add_action( 'quick_edit_custom_box', array( $this, 'quick_edit' ), 10, 2 );
67
		add_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ), 10, 2 );
68
		add_action( 'woocommerce_product_bulk_and_quick_edit', array( $this, 'bulk_and_quick_edit_save_post' ), 10, 2 );
69
	}
70
71
	/**
72
	 * Looks at the current screen and loads the correct list table handler.
73
	 *
74
	 * @since 3.3.0
75
	 */
76
	public function setup_screen() {
77
		global $wc_list_table;
78
79
		$screen_id = false;
80
81
		if ( function_exists( 'get_current_screen' ) ) {
82
			$screen    = get_current_screen();
83
			$screen_id = isset( $screen, $screen->id ) ? $screen->id : '';
84
		}
85
86
		if ( ! empty( $_REQUEST['screen'] ) ) { // WPCS: input var ok.
87
			$screen_id = wc_clean( wp_unslash( $_REQUEST['screen'] ) ); // WPCS: input var ok, sanitization ok.
88
		}
89
90
		switch ( $screen_id ) {
91
			case 'edit-shop_order':
92
				include_once 'list-tables/class-wc-admin-list-table-orders.php';
93
				$wc_list_table = new WC_Admin_List_Table_Orders();
94
				break;
95
			case 'edit-shop_coupon':
96
				include_once 'list-tables/class-wc-admin-list-table-coupons.php';
97
				$wc_list_table = new WC_Admin_List_Table_Coupons();
98
				break;
99
			case 'edit-product':
100
				include_once 'list-tables/class-wc-admin-list-table-products.php';
101
				$wc_list_table = new WC_Admin_List_Table_Products();
102
				break;
103
		}
104
105
		// Ensure the table handler is only loaded once. Prevents multiple loads if a plugin calls check_ajax_referer many times.
106
		remove_action( 'current_screen', array( $this, 'setup_screen' ) );
107
		remove_action( 'check_ajax_referer', array( $this, 'setup_screen' ) );
108
	}
109
110
	/**
111
	 * Change messages when a post type is updated.
112
	 *
113
	 * @param  array $messages Array of messages.
114
	 * @return array
115
	 */
116
	public function post_updated_messages( $messages ) {
117
		global $post;
118
119
		$messages['product'] = array(
120
			0  => '', // Unused. Messages start at index 1.
121
			/* translators: %s: Product view URL. */
122
			1  => sprintf( __( 'Product updated. <a href="%s">View Product</a>', 'woocommerce' ), esc_url( get_permalink( $post->ID ) ) ),
123
			2  => __( 'Custom field updated.', 'woocommerce' ),
124
			3  => __( 'Custom field deleted.', 'woocommerce' ),
125
			4  => __( 'Product updated.', 'woocommerce' ),
126
			5  => __( 'Revision restored.', 'woocommerce' ),
127
			/* translators: %s: product url */
128
			6  => sprintf( __( 'Product published. <a href="%s">View Product</a>', 'woocommerce' ), esc_url( get_permalink( $post->ID ) ) ),
129
			7  => __( 'Product saved.', 'woocommerce' ),
130
			/* translators: %s: product url */
131
			8  => sprintf( __( 'Product submitted. <a target="_blank" href="%s">Preview product</a>', 'woocommerce' ), esc_url( add_query_arg( 'preview', 'true', get_permalink( $post->ID ) ) ) ),
132
			9  => sprintf(
133
				/* translators: 1: date 2: product url */
134
				__( 'Product scheduled for: %1$s. <a target="_blank" href="%2$s">Preview product</a>', 'woocommerce' ),
135
				'<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ),
136
				esc_url( get_permalink( $post->ID ) ) . '</strong>'
137
			),
138
			/* translators: %s: product url */
139
			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 ) ) ) ),
140
		);
141
142
		$messages['shop_order'] = array(
143
			0  => '', // Unused. Messages start at index 1.
144
			1  => __( 'Order updated.', 'woocommerce' ),
145
			2  => __( 'Custom field updated.', 'woocommerce' ),
146
			3  => __( 'Custom field deleted.', 'woocommerce' ),
147
			4  => __( 'Order updated.', 'woocommerce' ),
148
			5  => __( 'Revision restored.', 'woocommerce' ),
149
			6  => __( 'Order updated.', 'woocommerce' ),
150
			7  => __( 'Order saved.', 'woocommerce' ),
151
			8  => __( 'Order submitted.', 'woocommerce' ),
152
			9  => sprintf(
153
				/* translators: %s: date */
154
				__( 'Order scheduled for: %s.', 'woocommerce' ),
155
				'<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ) . '</strong>'
156
			),
157
			10 => __( 'Order draft updated.', 'woocommerce' ),
158
			11 => __( 'Order updated and sent.', 'woocommerce' ),
159
		);
160
161
		$messages['shop_coupon'] = array(
162
			0  => '', // Unused. Messages start at index 1.
163
			1  => __( 'Coupon updated.', 'woocommerce' ),
164
			2  => __( 'Custom field updated.', 'woocommerce' ),
165
			3  => __( 'Custom field deleted.', 'woocommerce' ),
166
			4  => __( 'Coupon updated.', 'woocommerce' ),
167
			5  => __( 'Revision restored.', 'woocommerce' ),
168
			6  => __( 'Coupon updated.', 'woocommerce' ),
169
			7  => __( 'Coupon saved.', 'woocommerce' ),
170
			8  => __( 'Coupon submitted.', 'woocommerce' ),
171
			9  => sprintf(
172
				/* translators: %s: date */
173
				__( 'Coupon scheduled for: %s.', 'woocommerce' ),
174
				'<strong>' . date_i18n( __( 'M j, Y @ G:i', 'woocommerce' ), strtotime( $post->post_date ) ) . '</strong>'
175
			),
176
			10 => __( 'Coupon draft updated.', 'woocommerce' ),
177
		);
178
179
		return $messages;
180
	}
181
182
	/**
183
	 * Specify custom bulk actions messages for different post types.
184
	 *
185
	 * @param  array $bulk_messages Array of messages.
186
	 * @param  array $bulk_counts Array of how many objects were updated.
187
	 * @return array
188
	 */
189
	public function bulk_post_updated_messages( $bulk_messages, $bulk_counts ) {
190
		$bulk_messages['product'] = array(
191
			/* translators: %s: product count */
192
			'updated'   => _n( '%s product updated.', '%s products updated.', $bulk_counts['updated'], 'woocommerce' ),
193
			/* translators: %s: product count */
194
			'locked'    => _n( '%s product not updated, somebody is editing it.', '%s products not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
195
			/* translators: %s: product count */
196
			'deleted'   => _n( '%s product permanently deleted.', '%s products permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
197
			/* translators: %s: product count */
198
			'trashed'   => _n( '%s product moved to the Trash.', '%s products moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
199
			/* translators: %s: product count */
200
			'untrashed' => _n( '%s product restored from the Trash.', '%s products restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
201
		);
202
203
		$bulk_messages['shop_order'] = array(
204
			/* translators: %s: order count */
205
			'updated'   => _n( '%s order updated.', '%s orders updated.', $bulk_counts['updated'], 'woocommerce' ),
206
			/* translators: %s: order count */
207
			'locked'    => _n( '%s order not updated, somebody is editing it.', '%s orders not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
208
			/* translators: %s: order count */
209
			'deleted'   => _n( '%s order permanently deleted.', '%s orders permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
210
			/* translators: %s: order count */
211
			'trashed'   => _n( '%s order moved to the Trash.', '%s orders moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
212
			/* translators: %s: order count */
213
			'untrashed' => _n( '%s order restored from the Trash.', '%s orders restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
214
		);
215
216
		$bulk_messages['shop_coupon'] = array(
217
			/* translators: %s: coupon count */
218
			'updated'   => _n( '%s coupon updated.', '%s coupons updated.', $bulk_counts['updated'], 'woocommerce' ),
219
			/* translators: %s: coupon count */
220
			'locked'    => _n( '%s coupon not updated, somebody is editing it.', '%s coupons not updated, somebody is editing them.', $bulk_counts['locked'], 'woocommerce' ),
221
			/* translators: %s: coupon count */
222
			'deleted'   => _n( '%s coupon permanently deleted.', '%s coupons permanently deleted.', $bulk_counts['deleted'], 'woocommerce' ),
223
			/* translators: %s: coupon count */
224
			'trashed'   => _n( '%s coupon moved to the Trash.', '%s coupons moved to the Trash.', $bulk_counts['trashed'], 'woocommerce' ),
225
			/* translators: %s: coupon count */
226
			'untrashed' => _n( '%s coupon restored from the Trash.', '%s coupons restored from the Trash.', $bulk_counts['untrashed'], 'woocommerce' ),
227
		);
228
229
		return $bulk_messages;
230
	}
231
232
	/**
233
	 * Custom bulk edit - form.
234
	 *
235
	 * @param string $column_name Column being shown.
236
	 * @param string $post_type Post type being shown.
237
	 */
238 View Code Duplication
	public function bulk_edit( $column_name, $post_type ) {
239
		if ( 'price' !== $column_name || 'product' !== $post_type ) {
240
			return;
241
		}
242
243
		$shipping_class = get_terms(
244
			'product_shipping_class',
245
			array(
246
				'hide_empty' => false,
247
			)
248
		);
249
250
		include WC()->plugin_path() . '/includes/admin/views/html-bulk-edit-product.php';
251
	}
252
253
	/**
254
	 * Custom quick edit - form.
255
	 *
256
	 * @param string $column_name Column being shown.
257
	 * @param string $post_type Post type being shown.
258
	 */
259 View Code Duplication
	public function quick_edit( $column_name, $post_type ) {
260
		if ( 'price' !== $column_name || 'product' !== $post_type ) {
261
			return;
262
		}
263
264
		$shipping_class = get_terms(
265
			'product_shipping_class',
266
			array(
267
				'hide_empty' => false,
268
			)
269
		);
270
271
		include WC()->plugin_path() . '/includes/admin/views/html-quick-edit-product.php';
272
	}
273
274
	/**
275
	 * Offers a way to hook into save post without causing an infinite loop
276
	 * when quick/bulk saving product info.
277
	 *
278
	 * @since 3.0.0
279
	 * @param int    $post_id Post ID being saved.
280
	 * @param object $post Post object being saved.
281
	 */
282
	public function bulk_and_quick_edit_hook( $post_id, $post ) {
283
		remove_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ) );
284
		do_action( 'woocommerce_product_bulk_and_quick_edit', $post_id, $post );
285
		add_action( 'save_post', array( $this, 'bulk_and_quick_edit_hook' ), 10, 2 );
286
	}
287
288
	/**
289
	 * Quick and bulk edit saving.
290
	 *
291
	 * @param int    $post_id Post ID being saved.
292
	 * @param object $post Post object being saved.
293
	 * @return int
294
	 */
295
	public function bulk_and_quick_edit_save_post( $post_id, $post ) {
296
		// If this is an autosave, our form has not been submitted, so we don't want to do anything.
297
		if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
298
			return $post_id;
299
		}
300
301
		// Don't save revisions and autosaves.
302
		if ( wp_is_post_revision( $post_id ) || wp_is_post_autosave( $post_id ) || 'product' !== $post->post_type || ! current_user_can( 'edit_post', $post_id ) ) {
303
			return $post_id;
304
		}
305
306
		// Check nonce.
307
		if ( ! isset( $_REQUEST['woocommerce_quick_edit_nonce'] ) || ! wp_verify_nonce( $_REQUEST['woocommerce_quick_edit_nonce'], 'woocommerce_quick_edit_nonce' ) ) { // WPCS: input var ok, sanitization ok.
308
			return $post_id;
309
		}
310
311
		// Get the product and save.
312
		$product = wc_get_product( $post );
313
314
		if ( ! empty( $_REQUEST['woocommerce_quick_edit'] ) ) { // WPCS: input var ok.
315
			$this->quick_edit_save( $post_id, $product );
316
		} else {
317
			$this->bulk_edit_save( $post_id, $product );
318
		}
319
320
		return $post_id;
321
	}
322
323
	/**
324
	 * Quick edit.
325
	 *
326
	 * @param int        $post_id Post ID being saved.
327
	 * @param WC_Product $product Product object.
328
	 */
329
	private function quick_edit_save( $post_id, $product ) {
330
		$data_store        = $product->get_data_store();
331
		$old_regular_price = $product->get_regular_price();
332
		$old_sale_price    = $product->get_sale_price();
333
		$input_to_props    = array(
334
			'_weight'     => 'weight',
335
			'_length'     => 'length',
336
			'_width'      => 'width',
337
			'_height'     => 'height',
338
			'_visibility' => 'catalog_visibility',
339
			'_tax_class'  => 'tax_class',
340
			'_tax_status' => 'tax_status',
341
		);
342
343
		foreach ( $input_to_props as $input_var => $prop ) {
344
			if ( isset( $_REQUEST[ $input_var ] ) ) { // WPCS: input var ok, sanitization ok.
345
				$product->{"set_{$prop}"}( wc_clean( wp_unslash( $_REQUEST[ $input_var ] ) ) ); // WPCS: input var ok, sanitization ok.
346
			}
347
		}
348
349
		if ( isset( $_REQUEST['_sku'] ) ) { // WPCS: input var ok, sanitization ok.
350
			$sku     = $product->get_sku();
351
			$new_sku = (string) wc_clean( $_REQUEST['_sku'] ); // WPCS: input var ok, sanitization ok.
352
353
			if ( $new_sku !== $sku ) {
354
				if ( ! empty( $new_sku ) ) {
355
					$unique_sku = wc_product_has_unique_sku( $post_id, $new_sku );
356
					if ( $unique_sku ) {
357
						$product->set_sku( wc_clean( wp_unslash( $new_sku ) ) );
358
					}
359
				} else {
360
					$product->set_sku( '' );
361
				}
362
			}
363
		}
364
365 View Code Duplication
		if ( ! empty( $_REQUEST['_shipping_class'] ) ) { // WPCS: input var ok, sanitization ok.
366
			if ( '_no_shipping_class' === $_REQUEST['_shipping_class'] ) { // WPCS: input var ok, sanitization ok.
367
				$product->set_shipping_class_id( 0 );
368
			} else {
369
				$shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $_REQUEST['_shipping_class'] ) ); // WPCS: input var ok, sanitization ok.
370
				$product->set_shipping_class_id( $shipping_class_id );
371
			}
372
		}
373
374
		$product->set_featured( isset( $_REQUEST['_featured'] ) ); // WPCS: input var ok, sanitization ok.
375
376
		if ( $product->is_type( 'simple' ) || $product->is_type( 'external' ) ) {
377
378
			if ( isset( $_REQUEST['_regular_price'] ) ) { // WPCS: input var ok, sanitization ok.
379
				$new_regular_price = ( '' === $_REQUEST['_regular_price'] ) ? '' : wc_format_decimal( $_REQUEST['_regular_price'] ); // WPCS: input var ok, sanitization ok.
380
				$product->set_regular_price( $new_regular_price );
381
			} else {
382
				$new_regular_price = null;
383
			}
384
			if ( isset( $_REQUEST['_sale_price'] ) ) { // WPCS: input var ok, sanitization ok.
385
				$new_sale_price = ( '' === $_REQUEST['_sale_price'] ) ? '' : wc_format_decimal( $_REQUEST['_sale_price'] ); // WPCS: input var ok, sanitization ok.
386
				$product->set_sale_price( $new_sale_price );
387
			} else {
388
				$new_sale_price = null;
389
			}
390
391
			// Handle price - remove dates and set to lowest.
392
			$price_changed = false;
393
394
			if ( ! is_null( $new_regular_price ) && $new_regular_price !== $old_regular_price ) {
395
				$price_changed = true;
396
			} elseif ( ! is_null( $new_sale_price ) && $new_sale_price !== $old_sale_price ) {
397
				$price_changed = true;
398
			}
399
400
			if ( $price_changed ) {
401
				$product->set_date_on_sale_to( '' );
402
				$product->set_date_on_sale_from( '' );
403
			}
404
		}
405
406
		// Handle Stock Data.
407
		$manage_stock = ! empty( $_REQUEST['_manage_stock'] ) && 'grouped' !== $product->get_type() ? 'yes' : 'no'; // WPCS: input var ok, sanitization ok.
408
		$backorders   = ! empty( $_REQUEST['_backorders'] ) ? wc_clean( $_REQUEST['_backorders'] ) : 'no'; // WPCS: input var ok, sanitization ok.
409
		$stock_status = ! empty( $_REQUEST['_stock_status'] ) ? wc_clean( $_REQUEST['_stock_status'] ) : 'instock'; // WPCS: input var ok, sanitization ok.
410
		$stock_amount = 'yes' === $manage_stock && isset( $_REQUEST['_stock'] ) && is_numeric( wp_unslash( $_REQUEST['_stock'] ) ) ? wc_stock_amount( wp_unslash( $_REQUEST['_stock'] ) ) : ''; // WPCS: input var ok, sanitization ok.
411
412
		$product->set_manage_stock( $manage_stock );
413
		$product->set_backorders( $backorders );
414
415
		if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
416
			$product->set_stock_quantity( $stock_amount );
417
		}
418
419
		// Apply product type constraints to stock status.
420 View Code Duplication
		if ( $product->is_type( 'external' ) ) {
421
			// External products are always in stock.
422
			$product->set_stock_status( 'instock' );
423
		} elseif ( $product->is_type( 'variable' ) && ! $product->get_manage_stock() ) {
424
			// Stock status is determined by children.
425
			foreach ( $product->get_children() as $child_id ) {
426
				$child = wc_get_product( $child_id );
427
				if ( ! $product->get_manage_stock() ) {
428
					$child->set_stock_status( $stock_status );
429
					$child->save();
430
				}
431
			}
432
			$product = WC_Product_Variable::sync( $product, false );
433
		} else {
434
			$product->set_stock_status( $stock_status );
435
		}
436
437
		$product->save();
438
439
		do_action( 'woocommerce_product_quick_edit_save', $product );
440
	}
441
442
	/**
443
	 * Bulk edit.
444
	 *
445
	 * @param int        $post_id Post ID being saved.
446
	 * @param WC_Product $product Product object.
447
	 */
448
	public function bulk_edit_save( $post_id, $product ) {
449
		$data_store        = $product->get_data_store();
450
		$old_regular_price = $product->get_regular_price();
451
		$old_sale_price    = $product->get_sale_price();
452
		$data              = wp_unslash( $_REQUEST ); // WPCS: input var ok, CSRF ok.
453
454 View Code Duplication
		if ( ! empty( $_REQUEST['change_weight'] ) && isset( $_REQUEST['_weight'] ) ) { // WPCS: input var ok, sanitization ok.
455
			$product->set_weight( wc_clean( wp_unslash( $_REQUEST['_weight'] ) ) ); // WPCS: input var ok, sanitization ok.
456
		}
457
458
		if ( ! empty( $_REQUEST['change_dimensions'] ) ) { // WPCS: input var ok, sanitization ok.
459
			if ( isset( $_REQUEST['_length'] ) ) { // WPCS: input var ok, sanitization ok.
460
				$product->set_length( wc_clean( wp_unslash( $_REQUEST['_length'] ) ) ); // WPCS: input var ok, sanitization ok.
461
			}
462
			if ( isset( $_REQUEST['_width'] ) ) { // WPCS: input var ok, sanitization ok.
463
				$product->set_width( wc_clean( wp_unslash( $_REQUEST['_width'] ) ) ); // WPCS: input var ok, sanitization ok.
464
			}
465
			if ( isset( $_REQUEST['_height'] ) ) { // WPCS: input var ok, sanitization ok.
466
				$product->set_height( wc_clean( wp_unslash( $_REQUEST['_height'] ) ) ); // WPCS: input var ok, sanitization ok.
467
			}
468
		}
469
470
		if ( ! empty( $_REQUEST['_tax_status'] ) ) { // WPCS: input var ok, sanitization ok.
471
			$product->set_tax_status( wc_clean( $_REQUEST['_tax_status'] ) ); // WPCS: input var ok, sanitization ok.
472
		}
473
474
		if ( ! empty( $_REQUEST['_tax_class'] ) ) { // WPCS: input var ok, sanitization ok.
475
			$tax_class = wc_clean( wp_unslash( $_REQUEST['_tax_class'] ) ); // WPCS: input var ok, sanitization ok.
476
			if ( 'standard' === $tax_class ) {
477
				$tax_class = '';
478
			}
479
			$product->set_tax_class( $tax_class );
480
		}
481
482 View Code Duplication
		if ( ! empty( $_REQUEST['_shipping_class'] ) ) { // WPCS: input var ok, sanitization ok.
483
			if ( '_no_shipping_class' === $_REQUEST['_shipping_class'] ) { // WPCS: input var ok, sanitization ok.
484
				$product->set_shipping_class_id( 0 );
485
			} else {
486
				$shipping_class_id = $data_store->get_shipping_class_id_by_slug( wc_clean( $_REQUEST['_shipping_class'] ) ); // WPCS: input var ok, sanitization ok.
487
				$product->set_shipping_class_id( $shipping_class_id );
488
			}
489
		}
490
491
		if ( ! empty( $_REQUEST['_visibility'] ) ) { // WPCS: input var ok, sanitization ok.
492
			$product->set_catalog_visibility( wc_clean( $_REQUEST['_visibility'] ) ); // WPCS: input var ok, sanitization ok.
493
		}
494
495
		if ( ! empty( $_REQUEST['_featured'] ) ) { // WPCS: input var ok, sanitization ok.
496
			$product->set_featured( wp_unslash( $_REQUEST['_featured'] ) ); // WPCS: input var ok, sanitization ok.
497
		}
498
499
		if ( ! empty( $_REQUEST['_sold_individually'] ) ) { // WPCS: input var ok, sanitization ok.
500
			if ( 'yes' === $_REQUEST['_sold_individually'] ) { // WPCS: input var ok, sanitization ok.
501
				$product->set_sold_individually( 'yes' );
502
			} else {
503
				$product->set_sold_individually( '' );
504
			}
505
		}
506
507
		// Handle price - remove dates and set to lowest.
508
		$change_price_product_types    = apply_filters( 'woocommerce_bulk_edit_save_price_product_types', array( 'simple', 'external' ) );
509
		$can_product_type_change_price = false;
510
		foreach ( $change_price_product_types as $product_type ) {
511
			if ( $product->is_type( $product_type ) ) {
512
				$can_product_type_change_price = true;
513
				break;
514
			}
515
		}
516
517
		if ( $can_product_type_change_price ) {
518
			$price_changed = false;
519
520
			if ( ! empty( $_REQUEST['change_regular_price'] ) && isset( $_REQUEST['_regular_price'] ) ) { // WPCS: input var ok, sanitization ok.
521
				$change_regular_price = absint( $_REQUEST['change_regular_price'] ); // WPCS: input var ok, sanitization ok.
522
				$raw_regular_price    = wc_clean( wp_unslash( $_REQUEST['_regular_price'] ) ); // WPCS: input var ok, sanitization ok.
523
				$is_percentage        = (bool) strstr( $raw_regular_price, '%' );
524
				$regular_price        = wc_format_decimal( $raw_regular_price );
0 ignored issues
show
It seems like $raw_regular_price defined by wc_clean(wp_unslash($_REQUEST['_regular_price'])) on line 522 can also be of type array; however, wc_format_decimal() does only seem to accept double|string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
525
526
				switch ( $change_regular_price ) {
527
					case 1:
528
						$new_price = $regular_price;
529
						break;
530
					case 2:
531
						if ( $is_percentage ) {
532
							$percent   = $regular_price / 100;
533
							$new_price = $old_regular_price + ( round( $old_regular_price * $percent, wc_get_price_decimals() ) );
534
						} else {
535
							$new_price = $old_regular_price + $regular_price;
536
						}
537
						break;
538
					case 3:
539
						if ( $is_percentage ) {
540
							$percent   = $regular_price / 100;
541
							$new_price = max( 0, $old_regular_price - ( round( $old_regular_price * $percent, wc_get_price_decimals() ) ) );
542
						} else {
543
							$new_price = max( 0, $old_regular_price - $regular_price );
544
						}
545
						break;
546
547
					default:
548
						break;
549
				}
550
551
				if ( isset( $new_price ) && $new_price !== $old_regular_price ) {
552
					$price_changed = true;
553
					$new_price     = round( $new_price, wc_get_price_decimals() );
554
					$product->set_regular_price( $new_price );
555
				}
556
			}
557
558
			if ( ! empty( $_REQUEST['change_sale_price'] ) && isset( $_REQUEST['_sale_price'] ) ) { // WPCS: input var ok, sanitization ok.
559
				$change_sale_price = absint( $_REQUEST['change_sale_price'] ); // WPCS: input var ok, sanitization ok.
560
				$raw_sale_price    = wc_clean( wp_unslash( $_REQUEST['_sale_price'] ) ); // WPCS: input var ok, sanitization ok.
561
				$is_percentage     = (bool) strstr( $raw_sale_price, '%' );
562
				$sale_price        = wc_format_decimal( $raw_sale_price );
0 ignored issues
show
It seems like $raw_sale_price defined by wc_clean(wp_unslash($_REQUEST['_sale_price'])) on line 560 can also be of type array; however, wc_format_decimal() does only seem to accept double|string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
563
564
				switch ( $change_sale_price ) {
565
					case 1:
566
						$new_price = $sale_price;
567
						break;
568
					case 2:
569
						if ( $is_percentage ) {
570
							$percent   = $sale_price / 100;
571
							$new_price = $old_sale_price + ( $old_sale_price * $percent );
572
						} else {
573
							$new_price = $old_sale_price + $sale_price;
574
						}
575
						break;
576 View Code Duplication
					case 3:
577
						if ( $is_percentage ) {
578
							$percent   = $sale_price / 100;
579
							$new_price = max( 0, $old_sale_price - ( $old_sale_price * $percent ) );
580
						} else {
581
							$new_price = max( 0, $old_sale_price - $sale_price );
582
						}
583
						break;
584 View Code Duplication
					case 4:
585
						if ( $is_percentage ) {
586
							$percent   = $sale_price / 100;
587
							$new_price = max( 0, $product->regular_price - ( $product->regular_price * $percent ) );
588
						} else {
589
							$new_price = max( 0, $product->regular_price - $sale_price );
590
						}
591
						break;
592
593
					default:
594
						break;
595
				}
596
597
				if ( isset( $new_price ) && $new_price !== $old_sale_price ) {
598
					$price_changed = true;
599
					$new_price     = ! empty( $new_price ) || '0' === $new_price ? round( $new_price, wc_get_price_decimals() ) : '';
600
					$product->set_sale_price( $new_price );
601
				}
602
			}
603
604
			if ( $price_changed ) {
605
				$product->set_date_on_sale_to( '' );
606
				$product->set_date_on_sale_from( '' );
607
608
				if ( $product->get_regular_price() < $product->get_sale_price() ) {
609
					$product->set_sale_price( '' );
610
				}
611
			}
612
		}
613
614
		// Handle Stock Data.
615
		$was_managing_stock = $product->get_manage_stock() ? 'yes' : 'no';
616
		$stock_status       = $product->get_stock_status();
617
		$backorders         = $product->get_backorders();
618
		$backorders         = ! empty( $_REQUEST['_backorders'] ) ? wc_clean( $_REQUEST['_backorders'] ) : $backorders; // WPCS: input var ok, sanitization ok.
619
		$stock_status       = ! empty( $_REQUEST['_stock_status'] ) ? wc_clean( $_REQUEST['_stock_status'] ) : $stock_status; // WPCS: input var ok, sanitization ok.
620
621
		if ( ! empty( $_REQUEST['_manage_stock'] ) ) { // WPCS: input var ok, sanitization ok.
622
			$manage_stock = 'yes' === wc_clean( $_REQUEST['_manage_stock'] ) && 'grouped' !== $product->get_type() ? 'yes' : 'no'; // WPCS: input var ok, sanitization ok.
623
		} else {
624
			$manage_stock = $was_managing_stock;
625
		}
626
627
		$stock_amount = 'yes' === $manage_stock && ! empty( $_REQUEST['change_stock'] ) && isset( $_REQUEST['_stock'] ) ? wc_stock_amount( $_REQUEST['_stock'] ) : $product->get_stock_quantity(); // WPCS: input var ok, sanitization ok.
628
629
		$product->set_manage_stock( $manage_stock );
630
		$product->set_backorders( $backorders );
631
632
		if ( 'yes' === get_option( 'woocommerce_manage_stock' ) ) {
633
			$change_stock = absint( $_REQUEST['change_stock'] );
634
			switch ( $change_stock ) {
635
				case 2:
636
					wc_update_product_stock( $product, $stock_amount, 'increase', true );
637
					break;
638
				case 3:
639
					wc_update_product_stock( $product, $stock_amount, 'decrease', true );
640
					break;
641
				default:
642
					wc_update_product_stock( $product, $stock_amount, 'set', true );
643
					break;
644
			}
645
		}
646
647
		// Apply product type constraints to stock status.
648 View Code Duplication
		if ( $product->is_type( 'external' ) ) {
649
			// External products are always in stock.
650
			$product->set_stock_status( 'instock' );
651
		} elseif ( $product->is_type( 'variable' ) && ! $product->get_manage_stock() ) {
652
			// Stock status is determined by children.
653
			foreach ( $product->get_children() as $child_id ) {
654
				$child = wc_get_product( $child_id );
655
				if ( ! $product->get_manage_stock() ) {
656
					$child->set_stock_status( $stock_status );
657
					$child->save();
658
				}
659
			}
660
			$product = WC_Product_Variable::sync( $product, false );
661
		} else {
662
			$product->set_stock_status( $stock_status );
663
		}
664
665
		$product->save();
666
667
		do_action( 'woocommerce_product_bulk_edit_save', $product );
668
	}
669
670
	/**
671
	 * Disable the auto-save functionality for Orders.
672
	 */
673
	public function disable_autosave() {
674
		global $post;
675
676
		if ( $post && in_array( get_post_type( $post->ID ), wc_get_order_types( 'order-meta-boxes' ), true ) ) {
677
			wp_dequeue_script( 'autosave' );
678
		}
679
	}
680
681
	/**
682
	 * Output extra data on post forms.
683
	 *
684
	 * @param WP_Post $post Current post object.
685
	 */
686
	public function edit_form_top( $post ) {
687
		echo '<input type="hidden" id="original_post_title" name="original_post_title" value="' . esc_attr( $post->post_title ) . '" />';
688
	}
689
690
	/**
691
	 * Change title boxes in admin.
692
	 *
693
	 * @param string  $text Text to shown.
694
	 * @param WP_Post $post Current post object.
695
	 * @return string
696
	 */
697
	public function enter_title_here( $text, $post ) {
698
		switch ( $post->post_type ) {
699
			case 'product':
700
				$text = esc_html__( 'Product name', 'woocommerce' );
701
				break;
702
			case 'shop_coupon':
703
				$text = esc_html__( 'Coupon code', 'woocommerce' );
704
				break;
705
		}
706
		return $text;
707
	}
708
709
	/**
710
	 * Print coupon description textarea field.
711
	 *
712
	 * @param WP_Post $post Current post object.
713
	 */
714
	public function edit_form_after_title( $post ) {
715
		if ( 'shop_coupon' === $post->post_type ) {
716
			?>
717
			<textarea id="woocommerce-coupon-description" name="excerpt" cols="5" rows="2" placeholder="<?php esc_attr_e( 'Description (optional)', 'woocommerce' ); ?>"><?php echo $post->post_excerpt; // WPCS: XSS ok. ?></textarea>
718
			<?php
719
		}
720
	}
721
722
	/**
723
	 * Hidden default Meta-Boxes.
724
	 *
725
	 * @param  array  $hidden Hidden boxes.
726
	 * @param  object $screen Current screen.
727
	 * @return array
728
	 */
729
	public function hidden_meta_boxes( $hidden, $screen ) {
730
		if ( 'product' === $screen->post_type && 'post' === $screen->base ) {
731
			$hidden = array_merge( $hidden, array( 'postcustom' ) );
732
		}
733
734
		return $hidden;
735
	}
736
737
	/**
738
	 * Output product visibility options.
739
	 */
740
	public function product_data_visibility() {
741
		global $post, $thepostid, $product_object;
742
743
		if ( 'product' !== $post->post_type ) {
744
			return;
745
		}
746
747
		$thepostid          = $post->ID;
748
		$product_object     = $thepostid ? wc_get_product( $thepostid ) : new WC_Product();
749
		$current_visibility = $product_object->get_catalog_visibility();
750
		$current_featured   = wc_bool_to_string( $product_object->get_featured() );
751
		$visibility_options = wc_get_product_visibility_options();
752
		?>
753
		<div class="misc-pub-section" id="catalog-visibility">
754
			<?php esc_html_e( 'Catalog visibility:', 'woocommerce' ); ?>
755
			<strong id="catalog-visibility-display">
756
				<?php
757
758
				echo isset( $visibility_options[ $current_visibility ] ) ? esc_html( $visibility_options[ $current_visibility ] ) : esc_html( $current_visibility );
759
760
				if ( 'yes' === $current_featured ) {
761
					echo ', ' . esc_html__( 'Featured', 'woocommerce' );
762
				}
763
				?>
764
			</strong>
765
766
			<a href="#catalog-visibility" class="edit-catalog-visibility hide-if-no-js"><?php esc_html_e( 'Edit', 'woocommerce' ); ?></a>
767
768
			<div id="catalog-visibility-select" class="hide-if-js">
769
770
				<input type="hidden" name="current_visibility" id="current_visibility" value="<?php echo esc_attr( $current_visibility ); ?>" />
771
				<input type="hidden" name="current_featured" id="current_featured" value="<?php echo esc_attr( $current_featured ); ?>" />
772
773
				<?php
774
				echo '<p>' . esc_html__( 'This setting determines which shop pages products will be listed on.', 'woocommerce' ) . '</p>';
775
776
				foreach ( $visibility_options as $name => $label ) {
777
					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 />';
778
				}
779
780
				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 />';
781
				?>
782
				<p>
783
					<a href="#catalog-visibility" class="save-post-visibility hide-if-no-js button"><?php esc_html_e( 'OK', 'woocommerce' ); ?></a>
784
					<a href="#catalog-visibility" class="cancel-post-visibility hide-if-no-js"><?php esc_html_e( 'Cancel', 'woocommerce' ); ?></a>
785
				</p>
786
			</div>
787
		</div>
788
		<?php
789
	}
790
791
	/**
792
	 * Change upload dir for downloadable files.
793
	 *
794
	 * @param array $pathdata Array of paths.
795
	 * @return array
796
	 */
797
	public function upload_dir( $pathdata ) {
798
		if ( isset( $_POST['type'] ) && 'downloadable_product' === $_POST['type'] ) { // WPCS: CSRF ok, input var ok.
799
800
			if ( empty( $pathdata['subdir'] ) ) {
801
				$pathdata['path']   = $pathdata['path'] . '/woocommerce_uploads';
802
				$pathdata['url']    = $pathdata['url'] . '/woocommerce_uploads';
803
				$pathdata['subdir'] = '/woocommerce_uploads';
804
			} else {
805
				$new_subdir = '/woocommerce_uploads' . $pathdata['subdir'];
806
807
				$pathdata['path']   = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['path'] );
808
				$pathdata['url']    = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['url'] );
809
				$pathdata['subdir'] = str_replace( $pathdata['subdir'], $new_subdir, $pathdata['subdir'] );
810
			}
811
		}
812
		return $pathdata;
813
	}
814
815
	/**
816
	 * Run a filter when uploading a downloadable product.
817
	 */
818
	public function woocommerce_media_upload_downloadable_product() {
819
		do_action( 'media_upload_file' );
820
	}
821
822
	/**
823
	 * Grant downloadable file access to any newly added files on any existing.
824
	 * orders for this product that have previously been granted downloadable file access.
825
	 *
826
	 * @param int   $product_id product identifier.
827
	 * @param int   $variation_id optional product variation identifier.
828
	 * @param array $downloadable_files newly set files.
829
	 * @deprecated and moved to post-data class.
830
	 */
831
	public function process_product_file_download_paths( $product_id, $variation_id, $downloadable_files ) {
832
		WC_Post_Data::process_product_file_download_paths( $product_id, $variation_id, $downloadable_files );
833
	}
834
835
	/**
836
	 * When editing the shop page, we should hide templates.
837
	 *
838
	 * @param array   $page_templates Templates array.
839
	 * @param string  $theme Classname.
840
	 * @param WP_Post $post The current post object.
841
	 * @return array
842
	 */
843
	public function hide_cpt_archive_templates( $page_templates, $theme, $post ) {
844
		$shop_page_id = wc_get_page_id( 'shop' );
845
846
		if ( $post && absint( $post->ID ) === $shop_page_id ) {
847
			$page_templates = array();
848
		}
849
850
		return $page_templates;
851
	}
852
853
	/**
854
	 * Show a notice above the CPT archive.
855
	 *
856
	 * @param WP_Post $post The current post object.
857
	 */
858
	public function show_cpt_archive_notice( $post ) {
859
		$shop_page_id = wc_get_page_id( 'shop' );
860
861
		if ( $post && absint( $post->ID ) === $shop_page_id ) {
862
			echo '<div class="notice notice-info">';
863
			/* translators: %s: URL to read more about the shop page. */
864
			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://docs.woocommerce.com/document/woocommerce-pages/#section-4' ) . '</p>';
865
			echo '</div>';
866
		}
867
	}
868
869
	/**
870
	 * Add a post display state for special WC pages in the page list table.
871
	 *
872
	 * @param array   $post_states An array of post display states.
873
	 * @param WP_Post $post        The current post object.
874
	 */
875
	public function add_display_post_states( $post_states, $post ) {
876
		if ( wc_get_page_id( 'shop' ) === $post->ID ) {
877
			$post_states['wc_page_for_shop'] = __( 'Shop Page', 'woocommerce' );
878
		}
879
880
		if ( wc_get_page_id( 'cart' ) === $post->ID ) {
881
			$post_states['wc_page_for_cart'] = __( 'Cart Page', 'woocommerce' );
882
		}
883
884
		if ( wc_get_page_id( 'checkout' ) === $post->ID ) {
885
			$post_states['wc_page_for_checkout'] = __( 'Checkout Page', 'woocommerce' );
886
		}
887
888
		if ( wc_get_page_id( 'myaccount' ) === $post->ID ) {
889
			$post_states['wc_page_for_myaccount'] = __( 'My Account Page', 'woocommerce' );
890
		}
891
892
		if ( wc_get_page_id( 'terms' ) === $post->ID ) {
893
			$post_states['wc_page_for_terms'] = __( 'Terms and Conditions Page', 'woocommerce' );
894
		}
895
896
		return $post_states;
897
	}
898
}
899
900
new WC_Admin_Post_Types();
901