diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 3019353697d..00000000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - -## Prerequisites - - - -- [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate -- [ ] The issue still exists against the latest `master` branch of WooCommerce on Github (this is **not** the same version as on WordPress.org!) -- [ ] I have attempted to find the simplest possible steps to reproduce the issue -- [ ] I have included a failing test as a pull request (Optional) - -## Steps to reproduce the issue - - - -1. -2. -3. - -## Expected/actual behavior - -When I follow those steps, I see... - -I was expecting to see... - -## Isolating the problem - - - -- [ ] This bug happens with only WooCommerce plugin active -- [ ] This bug happens with a default WordPress theme active, or [Storefront](https://woocommerce.com/storefront/) -- [ ] I can reproduce this bug consistently using the steps above - -## WordPress Environment - -
-``` -Copy and paste the system status report from **WooCommerce > System Status** in WordPress admin here. -``` -
diff --git a/CHANGELOG.txt b/CHANGELOG.txt index b319f72b4d7..888f3534859 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,5 +1,10 @@ == Changelog == += 3.4.6 - 2018-10-11 = +* Fix - Security issues +* Fix - Allow percent coupons with sale restrictions to apply to carts with sale items in them. #21241 +* Fix - Prevent multiple slashing of variation's SKU. #21019 + = 3.4.5 - 2018-08-29 = * Fix - Tweak sanitization when resetting password cookie. #20901 * Fix - Use `+` instead of `array_merge` when appending parent to tax class to fix issues with numeric tax class names. #20916 diff --git a/includes/admin/importers/class-wc-product-csv-importer-controller.php b/includes/admin/importers/class-wc-product-csv-importer-controller.php index 4711b5bb29e..affa25fcaa0 100644 --- a/includes/admin/importers/class-wc-product-csv-importer-controller.php +++ b/includes/admin/importers/class-wc-product-csv-importer-controller.php @@ -83,6 +83,41 @@ class WC_Product_CSV_Importer_Controller { return new $importer_class( $file, $args ); } + /** + * Check whether a file is a valid CSV file. + * + * @param string $file File path. + * @param bool $check_path Whether to also check the file is located in a valid location (Default: true). + * @return bool + */ + public static function is_file_valid_csv( $file, $check_path = true ) { + if ( $check_path && apply_filters( 'woocommerce_product_csv_importer_check_import_file_path', true ) && 0 !== stripos( $file, ABSPATH ) ) { + return false; + } + + $valid_filetypes = self::get_valid_csv_filetypes(); + $filetype = wp_check_filetype( $file, $valid_filetypes ); + if ( in_array( $filetype['type'], $valid_filetypes, true ) ) { + return true; + } + + return false; + } + + /** + * Get all the valid filetypes for a CSV file. + * + * @return array + */ + protected static function get_valid_csv_filetypes() { + return apply_filters( + 'woocommerce_csv_product_import_valid_filetypes', array( + 'csv' => 'text/csv', + 'txt' => 'text/plain', + ) + ); + } + /** * Constructor. */ @@ -271,13 +306,6 @@ class WC_Product_CSV_Importer_Controller { * @return string|WP_Error */ public function handle_upload() { - $valid_filetypes = apply_filters( - 'woocommerce_csv_product_import_valid_filetypes', array( - 'csv' => 'text/csv', - 'txt' => 'text/plain', - ) - ); - // phpcs:disable WordPress.CSRF.NonceVerification.NoNonceVerification -- Nonce already verified in WC_Product_CSV_Importer_Controller::upload_form_handler() $file_url = isset( $_POST['file_url'] ) ? wc_clean( wp_unslash( $_POST['file_url'] ) ) : ''; @@ -286,14 +314,13 @@ class WC_Product_CSV_Importer_Controller { return new WP_Error( 'woocommerce_product_csv_importer_upload_file_empty', __( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.', 'woocommerce' ) ); } - $filetype = wp_check_filetype( wc_clean( wp_unslash( $_FILES['import']['name'] ) ), $valid_filetypes ); - if ( ! in_array( $filetype['type'], $valid_filetypes, true ) ) { + if ( ! self::is_file_valid_csv( wc_clean( wp_unslash( $_FILES['import']['name'] ) ), false ) ) { return new WP_Error( 'woocommerce_product_csv_importer_upload_file_invalid', __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) ); } $overrides = array( 'test_form' => false, - 'mimes' => $valid_filetypes, + 'mimes' => self::get_valid_csv_filetypes(), ); $import = $_FILES['import']; // WPCS: sanitization ok, input var ok. $upload = wp_handle_upload( $import, $overrides ); @@ -323,8 +350,7 @@ class WC_Product_CSV_Importer_Controller { return $upload['file']; } elseif ( file_exists( ABSPATH . $file_url ) ) { - $filetype = wp_check_filetype( ABSPATH . $file_url, $valid_filetypes ); - if ( ! in_array( $filetype['type'], $valid_filetypes, true ) ) { + if ( ! self::is_file_valid_csv( ABSPATH . $file_url ) ) { return new WP_Error( 'woocommerce_product_csv_importer_upload_file_invalid', __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) ); } @@ -372,8 +398,15 @@ class WC_Product_CSV_Importer_Controller { * Import the file if it exists and is valid. */ public function import() { + if ( ! self::is_file_valid_csv( $this->file ) ) { + $this->add_error( __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) ); + $this->output_errors(); + return; + } + if ( ! is_file( $this->file ) ) { $this->add_error( __( 'The file does not exist, please try again.', 'woocommerce' ) ); + $this->output_errors(); return; } diff --git a/includes/admin/settings/class-wc-settings-shipping.php b/includes/admin/settings/class-wc-settings-shipping.php index efc2d90c9a0..797551b0925 100644 --- a/includes/admin/settings/class-wc-settings-shipping.php +++ b/includes/admin/settings/class-wc-settings-shipping.php @@ -174,8 +174,11 @@ class WC_Settings_Shipping extends WC_Settings_Page { switch ( $current_section ) { case 'options': WC_Admin_Settings::save_fields( $this->get_settings() ); + do_action( 'woocommerce_update_options_' . $this->id . '_options' ); break; case 'classes': + do_action( 'woocommerce_update_options_' . $this->id . '_classes' ); + break; case '': break; default: @@ -189,10 +192,6 @@ class WC_Settings_Shipping extends WC_Settings_Page { break; } - if ( $current_section ) { - do_action( 'woocommerce_update_options_' . $this->id . '_' . $current_section ); - } - // Increments the transient version to invalidate cache. WC_Cache_Helper::get_transient_version( 'shipping', true ); } diff --git a/includes/admin/views/html-admin-page-status-logs.php b/includes/admin/views/html-admin-page-status-logs.php index 6f501191c00..0f5810e79b4 100644 --- a/includes/admin/views/html-admin-page-status-logs.php +++ b/includes/admin/views/html-admin-page-status-logs.php @@ -16,7 +16,7 @@ if ( ! defined( 'ABSPATH' ) ) {

- +

diff --git a/includes/class-wc-cart.php b/includes/class-wc-cart.php index 037a3817b21..1b3c3fda2be 100644 --- a/includes/class-wc-cart.php +++ b/includes/class-wc-cart.php @@ -401,7 +401,7 @@ class WC_Cart extends WC_Legacy_Cart { } /** - * Return all calculated coupon totals. + * Sets the array of calculated coupon totals. * * @since 3.2.0 * @param array $value Value to set. @@ -410,7 +410,7 @@ class WC_Cart extends WC_Legacy_Cart { $this->coupon_discount_totals = (array) $value; } /** - * Return all calculated coupon tax totals. + * Sets the array of calculated coupon tax totals. * * @since 3.2.0 * @param array $value Value to set. @@ -1317,6 +1317,7 @@ class WC_Cart extends WC_Legacy_Cart { 'postcode' => $this->get_customer()->get_shipping_postcode(), 'city' => $this->get_customer()->get_shipping_city(), 'address' => $this->get_customer()->get_shipping_address(), + 'address_1' => $this->get_customer()->get_shipping_address(), // Provide both address and address_1 for backwards compatibility. 'address_2' => $this->get_customer()->get_shipping_address_2(), ), 'cart_subtotal' => $this->get_displayed_subtotal(), diff --git a/includes/class-wc-download-handler.php b/includes/class-wc-download-handler.php index 8b1af51fe81..4bcabb74645 100644 --- a/includes/class-wc-download-handler.php +++ b/includes/class-wc-download-handler.php @@ -243,6 +243,14 @@ class WC_Download_Handler { $parsed_file_path = wp_parse_url( $file_path ); $remote_file = true; + // Paths that begin with '//' are always remote URLs. + if ( '//' === substr( $file_path, 0, 2 ) ) { + return array( + 'remote_file' => true, + 'file_path' => is_ssl() ? 'https:' . $file_path : 'http:' . $file_path, + ); + } + // See if path needs an abspath prepended to work. if ( file_exists( ABSPATH . $file_path ) ) { $remote_file = false; diff --git a/includes/class-wc-product-download.php b/includes/class-wc-product-download.php index 423197dc8ce..2bb73c4e5b1 100644 --- a/includes/class-wc-product-download.php +++ b/includes/class-wc-product-download.php @@ -87,7 +87,19 @@ class WC_Product_Download implements ArrayAccess { * @return boolean */ public function is_allowed_filetype() { - if ( 'relative' !== $this->get_type_of_file_path() ) { + $file_path = $this->get_file(); + + // File types for URL-based files located on the server should get validated. + $is_file_on_server = false; + if ( false !== stripos( $file_path, network_site_url( '/', 'https' ) ) || + false !== stripos( $file_path, network_site_url( '/', 'http' ) ) || + false !== stripos( $file_path, site_url( '/', 'https' ) ) || + false !== stripos( $file_path, site_url( '/', 'http' ) ) + ) { + $is_file_on_server = true; + } + + if ( ! $is_file_on_server && 'relative' !== $this->get_type_of_file_path() ) { return true; } return ! $this->get_file_extension() || in_array( $this->get_file_type(), $this->get_allowed_mime_types(), true ); diff --git a/includes/class-wc-query.php b/includes/class-wc-query.php index 5084ee96795..0eeacb4b6c0 100644 --- a/includes/class-wc-query.php +++ b/includes/class-wc-query.php @@ -459,6 +459,9 @@ class WC_Query { ); switch ( $orderby ) { + case 'id': + $args['orderby'] = 'ID'; + break; case 'menu_order': $args['orderby'] = 'menu_order title'; break; diff --git a/includes/class-wc-webhook.php b/includes/class-wc-webhook.php index bf4cf9dc1c8..5306f6e1b7e 100644 --- a/includes/class-wc-webhook.php +++ b/includes/class-wc-webhook.php @@ -442,7 +442,8 @@ class WC_Webhook extends WC_Legacy_Webhook { ); // Track failures. - if ( intval( $response_code ) >= 200 && intval( $response_code ) < 300 ) { + // Check for a success, which is a 2xx, 301 or 302 Response Code. + if ( intval( $response_code ) >= 200 && intval( $response_code ) < 303 ) { $this->set_failure_count( 0 ); $this->save(); } else { diff --git a/includes/import/class-wc-product-csv-importer.php b/includes/import/class-wc-product-csv-importer.php index 7ac517e5aeb..e7ff49a7645 100644 --- a/includes/import/class-wc-product-csv-importer.php +++ b/includes/import/class-wc-product-csv-importer.php @@ -63,6 +63,10 @@ class WC_Product_CSV_Importer extends WC_Product_Importer { * Read file. */ protected function read_file() { + if ( ! WC_Product_CSV_Importer_Controller::is_file_valid_csv( $this->file ) ) { + wp_die( __( 'Invalid file type. The importer supports CSV and TXT file formats.', 'woocommerce' ) ); + } + $handle = fopen( $this->file, 'r' ); // @codingStandardsIgnoreLine. if ( false !== $handle ) { diff --git a/includes/log-handlers/class-wc-log-handler-db.php b/includes/log-handlers/class-wc-log-handler-db.php index e654eb92b80..d47d850cc1b 100644 --- a/includes/log-handlers/class-wc-log-handler-db.php +++ b/includes/log-handlers/class-wc-log-handler-db.php @@ -146,8 +146,8 @@ class WC_Log_Handler_DB extends WC_Log_Handler { $wpdb->query( $wpdb->prepare( - "DELETE FROM {$wpdb->prefix}woocommerce_log WHERE timestamp < %d", - $timestamp + "DELETE FROM {$wpdb->prefix}woocommerce_log WHERE timestamp < %s", + date( 'Y-m-d H:i:s', $timestamp ) ) ); } diff --git a/includes/log-handlers/class-wc-log-handler-file.php b/includes/log-handlers/class-wc-log-handler-file.php index 0b233ef4a92..85514dbbfd9 100644 --- a/includes/log-handlers/class-wc-log-handler-file.php +++ b/includes/log-handlers/class-wc-log-handler-file.php @@ -248,9 +248,12 @@ class WC_Log_Handler_File extends WC_Log_Handler { */ public function remove( $handle ) { $removed = false; - $file = trailingslashit( WC_LOG_DIR ) . $handle; - if ( $file ) { - if ( is_file( $file ) && is_writable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable + $logs = $this->get_log_files(); + $handle = sanitize_title( $handle ); + + if ( isset( $logs[ $handle ] ) && $logs[ $handle ] ) { + $file = realpath( trailingslashit( WC_LOG_DIR ) . $logs[ $handle ] ); + if ( 0 === stripos( $file, trailingslashit( WC_LOG_DIR ) ) && is_file( $file ) && is_writable( $file ) ) { // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_is_writable $this->close( $file ); // Close first to be certain no processes keep it alive after it is unlinked. $removed = unlink( $file ); // phpcs:ignore WordPress.VIP.FileSystemWritesDisallow.file_ops_unlink } diff --git a/includes/wc-attribute-functions.php b/includes/wc-attribute-functions.php index 12339c6bdbd..b4a3616f0c2 100644 --- a/includes/wc-attribute-functions.php +++ b/includes/wc-attribute-functions.php @@ -536,16 +536,26 @@ function wc_create_attribute( $args ) { ); // Update product attributes which use this taxonomy. - $old_attribute_name_length = strlen( $old_slug ) + 3; - $attribute_name_length = strlen( $data['attribute_name'] ) + 3; - - $wpdb->query( + $old_taxonomy_name = 'pa_' . $old_slug; + $new_taxonomy_name = 'pa_' . $data['attribute_name']; + $metadatas = $wpdb->get_results( $wpdb->prepare( - "UPDATE {$wpdb->postmeta} SET meta_value = REPLACE( meta_value, %s, %s ) WHERE meta_key = '_product_attributes'", - 's:' . $old_attribute_name_length . ':"pa_' . $old_slug . '"', - 's:' . $attribute_name_length . ':"pa_' . $data['attribute_name'] . '"' - ) - ); + "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = '_product_attributes' AND meta_value LIKE %s", + '%' . $wpdb->esc_like( $old_taxonomy_name ) . '%' + ), + ARRAY_A ); + foreach ( $metadatas as $metadata ) { + $product_id = $metadata['post_id']; + $unserialized_data = maybe_unserialize( $metadata['meta_value'] ); + if ( ! $unserialized_data || ! is_array( $unserialized_data ) || ! isset( $unserialized_data[ $old_taxonomy_name ] ) ) { + continue; + } + + $unserialized_data[ $new_taxonomy_name ] = $unserialized_data[ $old_taxonomy_name ]; + unset( $unserialized_data[ $old_taxonomy_name ] ); + $unserialized_data[ $new_taxonomy_name ]['name'] = $new_taxonomy_name; + update_post_meta( $product_id, '_product_attributes', $unserialized_data ); + } // Update variations which use this taxonomy. $wpdb->update( diff --git a/includes/wc-core-functions.php b/includes/wc-core-functions.php index 6631d4339c4..f9ab2265457 100644 --- a/includes/wc-core-functions.php +++ b/includes/wc-core-functions.php @@ -1633,7 +1633,7 @@ function wc_get_logger() { $class = apply_filters( 'woocommerce_logging_class', 'WC_Logger' ); - if ( null !== $logger && is_a( $logger, $class ) ) { + if ( null !== $logger && is_string( $class ) && is_a( $logger, $class ) ) { return $logger; } diff --git a/includes/wc-order-functions.php b/includes/wc-order-functions.php index ee68ae42d6b..75ae8c08d3a 100644 --- a/includes/wc-order-functions.php +++ b/includes/wc-order-functions.php @@ -73,7 +73,7 @@ function wc_get_orders( $args ) { * * @param mixed $the_order Post object or post ID of the order. * - * @return bool|WC_Order|WC_Refund + * @return bool|WC_Order|WC_Order_Refund */ function wc_get_order( $the_order = false ) { if ( ! did_action( 'woocommerce_after_register_post_type' ) ) { diff --git a/includes/wc-template-hooks.php b/includes/wc-template-hooks.php index b37c7803563..a606bce240c 100644 --- a/includes/wc-template-hooks.php +++ b/includes/wc-template-hooks.php @@ -292,7 +292,7 @@ add_action( 'woocommerce_register_form', 'wc_registration_privacy_policy_text', /** * Notices. */ -add_action( 'woocommerce_cart_is_empty', 'woocommerce_output_all_notices', 10 ); +add_action( 'woocommerce_cart_is_empty', 'woocommerce_output_all_notices', 5 ); add_action( 'woocommerce_shortcode_before_product_cat_loop', 'woocommerce_output_all_notices', 10 ); add_action( 'woocommerce_before_shop_loop', 'woocommerce_output_all_notices', 10 ); add_action( 'woocommerce_before_single_product', 'woocommerce_output_all_notices', 10 ); diff --git a/includes/wc-user-functions.php b/includes/wc-user-functions.php index 90dae915926..b7b7a59926d 100644 --- a/includes/wc-user-functions.php +++ b/includes/wc-user-functions.php @@ -352,6 +352,12 @@ function wc_modify_editable_roles( $roles ) { if ( ! current_user_can( 'administrator' ) ) { unset( $roles['administrator'] ); } + + if ( current_user_can( 'shop_manager' ) ) { + $shop_manager_editable_roles = apply_filters( 'woocommerce_shop_manager_editable_roles', array( 'customer' ) ); + return array_intersect_key( $roles, array_flip( $shop_manager_editable_roles ) ); + } + return $roles; } add_filter( 'editable_roles', 'wc_modify_editable_roles' ); @@ -379,6 +385,15 @@ function wc_modify_map_meta_cap( $caps, $cap, $user_id, $args ) { if ( user_can( $args[0], 'administrator' ) && ! current_user_can( 'administrator' ) ) { $caps[] = 'do_not_allow'; } + + // Shop managers can only edit customer info. + if ( current_user_can( 'shop_manager' ) ) { + $userdata = get_userdata( $args[0] ); + $shop_manager_editable_roles = apply_filters( 'woocommerce_shop_manager_editable_roles', array( 'customer' ) ); + if ( property_exists( $userdata, 'roles' ) && ! empty( $userdata->roles ) && ! array_intersect( $userdata->roles, $shop_manager_editable_roles ) ) { + $caps[] = 'do_not_allow'; + } + } } break; } diff --git a/readme.txt b/readme.txt index a47b9feaa36..ccaf54692fe 100644 --- a/readme.txt +++ b/readme.txt @@ -13,7 +13,7 @@ WooCommerce is a powerful, extendable eCommerce plugin that helps you sell anyth WooCommerce is a free eCommerce plugin that allows you to sell anything, beautifully. Built to integrate seamlessly with WordPress, WooCommerce is the world’s favorite eCommerce solution that gives both store owners and developers complete control. -With endless flexibility and access to hundreds of free and premium WordPress extensions, WooCommerce now powers 30% of all online stores -- more than any other platform. +With endless flexibility and access to hundreds of free and premium WordPress extensions, WooCommerce now powers 30% of all online stores — more than any other platform. [youtube https://www.youtube.com/watch?v=1KahlicghaE] @@ -28,10 +28,10 @@ Offer free shipping, flat rate shipping, or make real-time calculations. Limit y = Extensive payment options = WooCommerce comes bundled with the ability to accept major credit cards, PayPal, BACS (bank transfers), and cash on delivery. Need additional options? More than 140 region-specific gateways integrate with WooCommerce, including popular choices like Stripe, Authorize.Net, and Amazon Payments. -= You control it all -- forever = += You control it all — forever = WooCommerce gives you complete control of your store, from taxes to stock levels to customer accounts. Add and remove extensions, change your design, and switch settings as you please. It’s all under your control. -One of the biggest risks of using a hosted eCommerce platform is what happens to your store if the provider closes up shop. With WooCommerce, you have complete control, so there’s never any reason to worry. Your data belongs to you -- and it’s kept secure, thanks to regular audits by industry leaders. +One of the biggest risks of using a hosted eCommerce platform is what happens to your store if the provider closes up shop. With WooCommerce, you have complete control, so there’s never any reason to worry. Your data belongs to you — and it’s kept secure, thanks to regular audits by industry leaders. = Define your style with Storefront = @@ -41,11 +41,11 @@ Define your style even further by customizing Storefront to your liking or choos = Built with developers in mind = -Extendable, adaptable, and open source -- WooCommerce was created with developers in mind. With its strong, robust framework, you can scale your client’s store all the way from basic to high-end (infinity and beyond). +Extendable, adaptable, and open source — WooCommerce was created with developers in mind. With its strong, robust framework, you can scale your client’s store all the way from basic to high-end (infinity and beyond). Built with a REST API, WooCommerce can integrate with virtually any service. Your store’s data can be accessed anywhere, anytime, 100% securely. WooCommerce allows developers to easily create, modify, and grow a store that meets their specifications. -No matter the size of the store you want to build, WooCommerce will scale to meet your requirements. With a growing collection of more than 300 extensions, you can enhance each store’s features to meet your client’s unique needs -- or even create your own solution. +No matter the size of the store you want to build, WooCommerce will scale to meet your requirements. With a growing collection of more than 300 extensions, you can enhance each store’s features to meet your client’s unique needs — or even create your own solution. If security is a concern, rest easy. WooCommerce is audited by a dedicated team of developers working around the clock to identify and patch any and all discovered bugs. diff --git a/templates/checkout/payment-method.php b/templates/checkout/payment-method.php index dead8df7953..0e8a654508a 100644 --- a/templates/checkout/payment-method.php +++ b/templates/checkout/payment-method.php @@ -10,24 +10,23 @@ * happen. When this occurs the version of the template file will be bumped and * the readme will list any important changes. * - * @see https://docs.woocommerce.com/document/template-structure/ - * @author WooThemes - * @package WooCommerce/Templates - * @version 2.3.0 + * @see https://docs.woocommerce.com/document/template-structure/ + * @package WooCommerce/Templates + * @version 3.5.0 */ if ( ! defined( 'ABSPATH' ) ) { exit; } ?> -
  • - chosen, true ); ?> data-order_button_text="order_button_text ); ?>" /> +
  • + chosen, true ); ?> data-order_button_text="order_button_text ); ?>" /> -