diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0aa8493 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,28 @@ + +# This file is for unifying the coding style for different editors and IDEs +# editorconfig.org + +# WordPress Coding Standards +# https://make.wordpress.org/core/handbook/coding-standards/ + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +tab_width = 4 +indent_style = tab +insert_final_newline = true +trim_trailing_whitespace = true + +[*.txt] +trim_trailing_whitespace = false + +[*.{md,json,yml}] +trim_trailing_whitespace = false +indent_style = space +indent_size = 2 + +[*.json] +indent_style = tab diff --git a/changelog.txt b/changelog.txt index 9227f9a..d15b087 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,12 @@ *** Woo Min/Max Quantities Changelog *** +2024.07.30 - version 5.0.0 +* Important - New: PHP 7.4+ is now required. +* Important - New: WooCommerce 8.2+ is now required. +* Important - New: WordPress 6.2+ is now required. +* Important - Removed all previously deprecated code. +* Tweak - Improved handling of orphaned variations to avoid fatal errors. + 2024.06.04 - version 4.3.2 * Tweak - Updated REST API validation to prevent incompatible quantity rules from being set. * Fix - Hide express checkout buttons on Single product page when Min/Max quantity can't be verified. diff --git a/includes/api/class-wc-mmq-rest-api.php b/includes/api/class-wc-mmq-rest-api.php index 6221b1f..2aaee5b 100644 --- a/includes/api/class-wc-mmq-rest-api.php +++ b/includes/api/class-wc-mmq-rest-api.php @@ -15,7 +15,7 @@ * Add custom REST API fields. * * @class WC_MMQ_REST_API - * @version 4.3.2 + * @version 5.0.0 */ class WC_MMQ_REST_API { @@ -267,7 +267,7 @@ private static function get_extended_variation_schema() { ), 'max_quantity' => array( 'description' => __( 'Maximum allowed variation quantity.', 'woocommerce-min-max-quantities' ), - 'type' => WC_MMQ_Core_Compatibility::is_wp_version_gte( '5.5' ) ? array( 'integer', 'string' ) : '', + 'type' => array( 'integer', 'string' ), 'context' => array( 'view', 'edit' ) ), 'exclude_order_quantity_value_rules' => array( @@ -330,7 +330,7 @@ private static function get_extended_product_schema() { ), 'max_quantity' => array( 'description' => __( 'Maximum allowed product quantity.', 'woocommerce-min-max-quantities' ), - 'type' => WC_MMQ_Core_Compatibility::is_wp_version_gte( '5.5' ) ? array( 'integer', 'string' ) : '', + 'type' => array( 'integer', 'string' ), 'context' => array( 'view', 'edit' ) ), 'exclude_order_quantity_value_rules' => array( @@ -564,7 +564,12 @@ private static function get_product_field( $key, $product ) { $value = (int) $product->get_meta( 'variation_minimum_allowed_quantity', true ); } else { $parent_product = wc_get_product( $product->get_parent_id() ); - $value = (int) $parent_product->get_meta( 'minimum_allowed_quantity', true ); + + if ( ! is_a( $parent_product, 'WC_Product' ) ) { + return 0; + } + + $value = (int) $parent_product->get_meta( 'minimum_allowed_quantity', true ); } } else { $value = (int) $product->get_meta( 'minimum_allowed_quantity', true ); @@ -578,7 +583,12 @@ private static function get_product_field( $key, $product ) { $max_quantity = $product->get_meta( 'variation_maximum_allowed_quantity', true ); } else { $parent_product = wc_get_product( $product->get_parent_id() ); - $max_quantity = $parent_product->get_meta( 'maximum_allowed_quantity', true ); + + if ( ! is_a( $parent_product, 'WC_Product' ) ) { + return ''; + } + + $max_quantity = $parent_product->get_meta( 'maximum_allowed_quantity', true ); } } else { $max_quantity = $product->get_meta( 'maximum_allowed_quantity', true ); @@ -594,7 +604,12 @@ private static function get_product_field( $key, $product ) { $value = $product->get_meta( 'variation_minmax_cart_exclude', true ); } else { $parent_product = wc_get_product( $product->get_parent_id() ); - $value = $parent_product->get_meta( 'minmax_cart_exclude', true ); + + if ( ! is_a( $parent_product, 'WC_Product' ) ) { + return 'no'; + } + + $value = $parent_product->get_meta( 'minmax_cart_exclude', true ); } } else { $value = $product->get_meta( 'minmax_cart_exclude', true ); @@ -612,7 +627,12 @@ private static function get_product_field( $key, $product ) { $value = $product->get_meta( 'variation_minmax_category_group_of_exclude', true ); } else { $parent_product = wc_get_product( $product->get_parent_id() ); - $value = $parent_product->get_meta( 'minmax_category_group_of_exclude', true ); + + if ( ! is_a( $parent_product, 'WC_Product' ) ) { + return 'no'; + } + + $value = $parent_product->get_meta( 'minmax_category_group_of_exclude', true ); } } else { $value = $product->get_meta( 'minmax_category_group_of_exclude', true ); diff --git a/languages/woocommerce-min-max-quantities.pot b/languages/woocommerce-min-max-quantities.pot index bbdf743..6e254c9 100644 --- a/languages/woocommerce-min-max-quantities.pot +++ b/languages/woocommerce-min-max-quantities.pot @@ -2,14 +2,14 @@ # This file is distributed under the GNU General Public License v3.0. msgid "" msgstr "" -"Project-Id-Version: Woo Min/Max Quantities 4.3.2\n" +"Project-Id-Version: Woo Min/Max Quantities 5.0.0\n" "Report-Msgid-Bugs-To: https://woocommerce.com/my-account/create-a-ticket/\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"POT-Creation-Date: 2024-06-04T09:20:25+00:00\n" +"POT-Creation-Date: 2024-07-30T07:54:56+00:00\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "X-Generator: WP-CLI 2.10.0\n" "language-team: LANGUAGE \n" @@ -365,106 +365,105 @@ msgstr "" msgid "Foul!" msgstr "" -#. translators: Minimum required WooCommerce version -#: woocommerce-min-max-quantities.php:271 +#: woocommerce-min-max-quantities.php:275 msgid "Min/Max Quantities requires at least WooCommerce %s." msgstr "" #. translators: Minimum required PHP version -#: woocommerce-min-max-quantities.php:286 +#: woocommerce-min-max-quantities.php:295 msgid "Woo Min/Max Quantities requires at least PHP %1$s. Learn how to update PHP." msgstr "" -#: woocommerce-min-max-quantities.php:736 +#: woocommerce-min-max-quantities.php:745 msgid "excludes " msgstr "" #. translators: %d: Minimum amount of items in the cart -#: woocommerce-min-max-quantities.php:741 +#: woocommerce-min-max-quantities.php:750 msgid "To place an order, your cart must contain at least %d items." msgstr "" #. translators: %d: Maximum amount of items in the cart -#: woocommerce-min-max-quantities.php:747 +#: woocommerce-min-max-quantities.php:756 msgid "Your cart must not contain more than %d items to place an order." msgstr "" #. translators: %s: Minimum order value -#: woocommerce-min-max-quantities.php:756 +#: woocommerce-min-max-quantities.php:765 msgid "To place an order, your cart total must be at least %s." msgstr "" #. translators: %s: Maximum order value -#: woocommerce-min-max-quantities.php:763 +#: woocommerce-min-max-quantities.php:772 msgid "Your cart total must not be higher than %s to place an order." msgstr "" #. translators: %1$s: Category name, %2$s: Comma separated list of product names, %3$d: Group amount -#: woocommerce-min-max-quantities.php:793 +#: woocommerce-min-max-quantities.php:802 msgid "Products in the %1$s category (%2$s) must be bought in multiples of %3$d." msgstr "" -#: woocommerce-min-max-quantities.php:846 +#: woocommerce-min-max-quantities.php:855 msgid "Available on backorder" msgstr "" -#: woocommerce-min-max-quantities.php:851 +#: woocommerce-min-max-quantities.php:860 msgid "Out of stock" msgstr "" #. translators: %1$s: Product name, %2$s: Minimum order quantity, %3$s: Total cart quantity -#: woocommerce-min-max-quantities.php:895 +#: woocommerce-min-max-quantities.php:909 msgid "To place an order, the quantity of \"%1$s\" must be at least %2$s. You currently have %3$s in your cart." msgstr "" #. translators: %1$s: Product name, %2$s: Minimum order quantity -#: woocommerce-min-max-quantities.php:903 +#: woocommerce-min-max-quantities.php:917 msgid "The quantity of \"%1$s\" has been increased to %2$s. This is the minimum required quantity." msgstr "" #. translators: %1$s: Product name, %2$s: Minimum order quantity -#: woocommerce-min-max-quantities.php:906 +#: woocommerce-min-max-quantities.php:920 msgid "To place an order, the quantity of \"%1$s\" must be at least %2$s." msgstr "" #. translators: %1$s: Product name, %2$s: Maximum order quantity, %3$s: Total cart quantity -#: woocommerce-min-max-quantities.php:916 +#: woocommerce-min-max-quantities.php:930 msgid "The quantity of \"%1$s\" cannot be higher than %2$s to place an order. You currently have %3$s in your cart." msgstr "" #. translators: %1$s: Product name, %2$s: Maximum order quantity -#: woocommerce-min-max-quantities.php:924 +#: woocommerce-min-max-quantities.php:938 msgid "The quantity of \"%1$s\" has been decreased to %2$s. This is the maximum allowed quantity." msgstr "" #. translators: %1$s: Product name, %2$s: Maximum order quantity -#: woocommerce-min-max-quantities.php:927 +#: woocommerce-min-max-quantities.php:941 msgid "The quantity of \"%1$s\" cannot be higher than %2$s to place an order." msgstr "" #. translators: %1$s: Product name, %2$d: Group amount -#: woocommerce-min-max-quantities.php:939 -#: woocommerce-min-max-quantities.php:950 +#: woocommerce-min-max-quantities.php:953 +#: woocommerce-min-max-quantities.php:964 msgid "\"%1$s\" must be bought in multiples of %2$d. Please adjust its quantity to continue." msgstr "" #. translators: %1$s: Product name, %2$d: Group amount -#: woocommerce-min-max-quantities.php:947 +#: woocommerce-min-max-quantities.php:961 msgid "The quantity of \"%1$s\" has been adjusted. \"%1$s\" must be bought in multiples of %2$d." msgstr "" #. translators: %1$s: Product name, %2$d: Group of quantity -#: woocommerce-min-max-quantities.php:1048 +#: woocommerce-min-max-quantities.php:1062 msgid "\"%1$s\" can only be bought in multiples of %2$d." msgstr "" #. translators: %1$s: Product name, %2$d: Minimum Quantity -#: woocommerce-min-max-quantities.php:1069 +#: woocommerce-min-max-quantities.php:1083 msgid "The minimum required quantity for \"%1$s\" is %2$d." msgstr "" #. translators: %1$s: Product name, %2$d: Maximum quantity -#: woocommerce-min-max-quantities.php:1092 +#: woocommerce-min-max-quantities.php:1106 msgid "The maximum allowed quantity for \"%1$s\" is %2$d." msgstr "" diff --git a/woocommerce-min-max-quantities.php b/woocommerce-min-max-quantities.php index 3642994..096f558 100644 --- a/woocommerce-min-max-quantities.php +++ b/woocommerce-min-max-quantities.php @@ -3,13 +3,17 @@ * Plugin Name: Woo Min/Max Quantities * Plugin URI: https://woocommerce.com/products/minmax-quantities/ * Description: Define minimum/maximum allowed quantities for products, variations and orders. - * Version: 4.3.2 + * Version: 5.0.0 * Author: Woo * Author URI: https://woocommerce.com - * Requires at least: 4.4 - * Tested up to: 6.5 - * WC tested up to: 8.9 - * WC requires at least: 3.9.0 + * + * Requires PHP: 7.4 + * + * Requires at least: 6.2 + * Tested up to: 6.6 + * + * WC tested up to: 9.1 + * WC requires at least: 8.2 * * Requires Plugins: woocommerce * @@ -26,7 +30,7 @@ if ( ! class_exists( 'WC_Min_Max_Quantities' ) ) : - define( 'WC_MIN_MAX_QUANTITIES', '4.3.2' ); // WRCS: DEFINED_VERSION. + define( 'WC_MIN_MAX_QUANTITIES', '5.0.0' ); // WRCS: DEFINED_VERSION. /** * Min Max Quantities class. @@ -38,7 +42,7 @@ class WC_Min_Max_Quantities { * * @var string */ - public $min_wc_version = '3.9.0'; + public $min_wc_version = '8.2.0'; /** * Minimum order quantity. @@ -113,12 +117,10 @@ public function __construct() { if ( ! function_exists( 'WC' ) || version_compare( WC()->version, $this->min_wc_version ) < 0 ) { add_action( 'admin_notices', array( $this, 'woocommerce_required_notice' ) ); - return; } - if ( ! function_exists( 'phpversion' ) || version_compare( phpversion(), '7.0.0', '<' ) ) { + if ( ! function_exists( 'phpversion' ) || version_compare( phpversion(), '7.4.0', '<' ) ) { add_action( 'admin_notices', array( $this, 'php_version_notice' ) ); - return; } $this->maybe_define_constant( 'WC_MMQ_ABSPATH', trailingslashit( plugin_dir_path( __FILE__ ) ) ); @@ -268,7 +270,14 @@ public function woocommerce_required_notice() {

Min/Max Quantities requires at least WooCommerce %s.', 'woocommerce-min-max-quantities' ), esc_html( $this->min_wc_version ) ) ); + echo wp_kses_post( + sprintf( + __( + 'Min/Max Quantities requires at least WooCommerce %s.', + 'woocommerce-min-max-quantities' ), + esc_html( $this->min_wc_version ) + ) + ); ?>

%1$s. Learn how to update PHP.', 'woocommerce-min-max-quantities' ), '7.0.0', 'https://woocommerce.com/document/how-to-update-your-php-version/' ) ); + echo wp_kses_post( sprintf( __( 'Woo Min/Max Quantities requires at least PHP %1$s. Learn how to update PHP.', 'woocommerce-min-max-quantities' ), '7.4.0', 'https://woocommerce.com/document/how-to-update-your-php-version/' ) ); ?>

get_title(); } @@ -1398,7 +1412,7 @@ public function available_variation( $data, $product, $variation ) { * Get group_of_quantity setting for a product. * * @param WC_Product $product Product object. - * + * * Doesn't handle variations on variable products. * * @return int @@ -1578,7 +1592,7 @@ public static function adjust_max_quantity( $max_quantity, $group_of_quantity, $ /** * Check if the product always conforms to MMQ rules. - * + * * That is, there are either no rules set, or the min/max rules are set and equal. * * @param WC_Product $product Product object. @@ -1586,7 +1600,7 @@ public static function adjust_max_quantity( $max_quantity, $group_of_quantity, $ * @return bool */ private function can_skip_product_rules_validation( $product ) { - + if ( ! $product ) { return false; } @@ -1611,13 +1625,13 @@ private function can_skip_product_rules_validation( $product ) { /** * Check if the variable product supports express checkout. - * + * * Simplified version that is a bit lighter on resources: If there are any variations with min/max rules enabled, return false. * Variations can have different prices, too. But that's handled outside of this plugin. - * + * * @param WC_Product $product Product object. - * + * * @return bool */ private function variable_product_supports_express_checkout( $product ) { @@ -1627,13 +1641,13 @@ private function variable_product_supports_express_checkout( $product ) { } if ( ! in_array( $product->get_type(), array( 'variable', 'variable-subscription' ), true ) ) { - // This is counterintuitive, but if a non-variable product is passed, + // This is counterintuitive, but if a non-variable product is passed, // we shouldn't forbid the express checkout button here. return true; } $combine_variations = $product->get_meta( 'allow_combination', true ); - + if ( 'yes' === $combine_variations ) { /* If Combine variations is set to true, there are no rules on the variations. But if min == max on the variable product with combined variations, qty is not set automatically. @@ -1662,17 +1676,17 @@ private function variable_product_supports_express_checkout( $product ) { /** * Check if global rules apply to a product. - * + * * That is, if there are any global rules set, and the product is not excluded from them. - * + * * Exclusion from group of rules applied through categories is done in can_skip_product_rules_validation(), * as get_group_of_quantity_for_product returns 0 if the rule is set, but the product is excluded. - * + * * @since 4.3.2 * @version 4.3.2 - * + * * @param WC_Product $product Product object. - * + * * @return bool */ private function global_rules_apply_to_product( $product ) { @@ -1705,40 +1719,40 @@ private function global_rules_apply_to_product( $product ) { /** * Check if the express checkout button(s) can be displayed on single product page. - * + * * The only thing we can do from PHP is to hide the smart buttons if the min/max quantity or value is set. * The end customer can increase the quantity and click the express checkout button and only at that point * we can check if the quantity/value is within the allowed range. * Since we can't validate this in PHP, we hide the express checkout button. - * + * * @since 4.3.2 * @version 4.3.2 - * + * * @param WC_Product $product Product object. - * + * * @return bool */ public function can_display_express_checkout( $product ) { - /* If global MMQ rules apply to the product, + /* If global MMQ rules apply to the product, we can't verify the quantity/value in PHP, so we can't display the express checkout button. */ if ( $this->global_rules_apply_to_product( $product ) ) { return false; } - /* If the product has rules that need to be validated, + /* If the product has rules that need to be validated, (and we can't verify the quantity in PHP), we can't display the express checkout button. */ if ( ! $this->can_skip_product_rules_validation( $product ) ) { return false; } - /* If the product is a variable product and there are variations with min/max rules, + /* If the product is a variable product and there are variations with min/max rules, we can't verify the quantity in PHP, so we can't display the express checkout button. - + We can test for variable products after the test for simple products, because all the cases that are handled incorrectly by product_always_conforms_to_rules() allow this check to run: - if there are no rules on variations, product_always_conforms_to_rules is almost correct for variable products, too. - - if there are no rules on variable product, but there are rules on variation + - if there are no rules on variable product, but there are rules on variation -> product_always_conforms_to_rules will return true, but this will be corrected to false here. - if min == max on the variable product, but there are rules on the variation -> product_always_conforms_to_rules will return true, but this will be corrected to false here.