diff --git a/package.json b/package.json index 735824f2..c6b0633a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "woocommerce-germanized-shipments", "title": "Shipments for WooCommerce", - "version": "3.0.5", + "version": "3.1.1", "homepage": "https://vendidero.de", "repository": { "type": "git", diff --git a/src/Automation.php b/src/Automation.php index 77baf76b..3a171373 100644 --- a/src/Automation.php +++ b/src/Automation.php @@ -220,12 +220,11 @@ public static function create_shipments( $order ) { if ( $order_shipment = wc_gzd_get_shipment_order( $order ) ) { /** - * Sync existing shipments and items before creating new shipments + * Sync existing shipments before creating new shipments */ foreach ( $order_shipment->get_simple_shipments() as $shipment ) { if ( $shipment->is_editable() ) { $shipment->sync(); - $shipment->sync_items(); $shipment->save(); } } diff --git a/src/Package.php b/src/Package.php index c982f3e2..ffcd3c7c 100644 --- a/src/Package.php +++ b/src/Package.php @@ -18,7 +18,7 @@ class Package { * * @var string */ - const VERSION = '3.0.5'; + const VERSION = '3.1.1'; public static $upload_dir_suffix = ''; @@ -424,11 +424,15 @@ public static function register_endpoints( $query_vars ) { public static function install() { self::init(); + + if ( ! self::has_dependencies() ) { + return; + } + Install::install(); } public static function install_integration() { - self::init(); self::install(); } @@ -478,6 +482,14 @@ protected static function generate_key() { return md5( serialize( $key ) ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize } + public static function is_shipping_debug_mode() { + return apply_filters( 'woocommerce_gzd_shipments_is_shipping_debug_mode', 'yes' === get_option( 'woocommerce_shipping_debug_mode', 'no' ) ); + } + + public static function is_constant_defined( $constant ) { + return class_exists( 'Automattic\Jetpack\Constants' ) ? \Automattic\Jetpack\Constants::is_defined( $constant ) : defined( $constant ); + } + public static function log( $message, $type = 'info', $source = '' ) { $enable_logging = defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false; diff --git a/src/Packaging.php b/src/Packaging.php index 10227452..8c506c1d 100644 --- a/src/Packaging.php +++ b/src/Packaging.php @@ -279,6 +279,12 @@ public function has_inner_dimensions() { return ! empty( $this->get_inner_width( 'edit' ) ) || ! empty( $this->get_inner_length( 'edit' ) ) || ! empty( $this->get_inner_height( 'edit' ) ); } + public function has_shipping_class_restrictions() { + $classes = $this->get_available_shipping_classes( 'edit' ); + + return ! empty( $classes ) ? true : false; + } + /** * Returns the available shipping provider names. * @@ -315,7 +321,9 @@ public function supports_shipping_class( $shipping_class ) { $classes = $this->get_available_shipping_classes( 'edit' ); $supports = false; - if ( empty( $classes ) || in_array( $shipping_class, $classes, true ) ) { + if ( empty( $shipping_class ) && empty( $classes ) ) { + $supports = true; + } elseif ( empty( $classes ) || in_array( $shipping_class, $classes, true ) ) { $supports = true; } diff --git a/src/Packing/CartItem.php b/src/Packing/CartItem.php index 830df78e..39d44591 100644 --- a/src/Packing/CartItem.php +++ b/src/Packing/CartItem.php @@ -48,8 +48,7 @@ public function __construct( $item, $incl_taxes = false ) { $line_subtotal += (int) wc_add_number_precision( $this->item['line_subtotal_tax'] ); } - $this->weight = (int) wc_get_weight( $weight, 'g' ); - + $this->weight = (int) wc_get_weight( $weight, 'g' ); $this->total = $quantity > 0 ? NumberUtil::round( $line_total / $quantity ) : 0; $this->subtotal = $quantity > 0 ? NumberUtil::round( $line_subtotal / $quantity ) : 0; } diff --git a/src/Packing/Item.php b/src/Packing/Item.php index 3a395ef0..d1d65ffd 100644 --- a/src/Packing/Item.php +++ b/src/Packing/Item.php @@ -60,7 +60,7 @@ public function canBePacked( $box, $already_packed_items, int $proposed_x, int $ if ( $product = $this->get_product() ) { $shipping_class = $product->get_shipping_class_id(); - if ( ! empty( $shipping_class ) ) { + if ( $box->get_packaging()->has_shipping_class_restrictions() ) { if ( ! $box->get_packaging()->supports_shipping_class( $shipping_class ) ) { $fits = false; } diff --git a/src/Shipment.php b/src/Shipment.php index 320c0772..dabaed33 100644 --- a/src/Shipment.php +++ b/src/Shipment.php @@ -273,13 +273,17 @@ public function get_shipping_zone() { } public function is_shipping_domestic() { - return Package::is_shipping_domestic( - $this->get_country(), - array( - 'sender_country' => $this->get_sender_country(), - 'sender_postcode' => $this->get_sender_postcode(), - 'postcode' => $this->get_postcode(), - ) + return apply_filters( + "{$this->get_general_hook_prefix()}is_shipping_domestic", + Package::is_shipping_domestic( + $this->get_country(), + array( + 'sender_country' => $this->get_sender_country(), + 'sender_postcode' => $this->get_sender_postcode(), + 'postcode' => $this->get_postcode(), + ) + ), + $this ); } @@ -290,13 +294,17 @@ public function is_shipping_domestic() { * @return bool */ public function is_shipping_inner_eu() { - if ( Package::is_shipping_inner_eu_country( - $this->get_country(), - array( - 'sender_country' => $this->get_sender_country(), - 'sender_postcode' => $this->get_sender_postcode(), - 'postcode' => $this->get_postcode(), - ) + if ( apply_filters( + "{$this->get_general_hook_prefix()}is_shipping_inner_eu", + Package::is_shipping_inner_eu_country( + $this->get_country(), + array( + 'sender_country' => $this->get_sender_country(), + 'sender_postcode' => $this->get_sender_postcode(), + 'postcode' => $this->get_postcode(), + ) + ), + $this ) ) { return true; } diff --git a/src/ShippingMethod/ShippingMethod.php b/src/ShippingMethod/ShippingMethod.php index 9a195f74..c8afde27 100644 --- a/src/ShippingMethod/ShippingMethod.php +++ b/src/ShippingMethod/ShippingMethod.php @@ -538,10 +538,13 @@ public function get_available_packaging_boxes( $package_data = array() ) { public function calculate_shipping( $package = array() ) { $applied_rules = array(); + $debug_notices = array(); + $is_debug_mode = Package::is_shipping_debug_mode(); if ( isset( $package['items_to_pack'], $package['package_data'] ) ) { $cart_data = (array) $package['package_data']; - $boxes = \DVDoug\BoxPacker\BoxList::fromArray( $this->get_available_packaging_boxes( $cart_data ) ); + $available_boxes = $this->get_available_packaging_boxes( $cart_data ); + $boxes = \DVDoug\BoxPacker\BoxList::fromArray( $available_boxes ); $cost_calculation_mode = $this->get_multiple_shipments_cost_calculation_mode(); $multiple_rules_calculation_mode = $this->get_multiple_rules_cost_calculation_mode(); @@ -551,83 +554,56 @@ public function calculate_shipping( $package = array() ) { $total_packed_item_map = array(); $total_packed_items = 0; $packed_boxes = Helper::pack( $package['items_to_pack'], $boxes, 'cart' ); + $unpacked_items = Helper::get_last_unpacked_items(); + + if ( 0 === count( $unpacked_items ) ) { + foreach ( $packed_boxes as $box ) { + $packaging = $box->getBox(); + $items = $box->getItems(); + $total_weight = wc_get_weight( $items->getWeight(), strtolower( get_option( 'woocommerce_weight_unit' ) ), 'g' ); + $volume = wc_get_dimension( $items->getVolume(), strtolower( get_option( 'woocommerce_dimension_unit' ) ), 'mm' ); + $item_count = $items->count(); + $total = 0; + $subtotal = 0; + $products = array(); + $shipping_classes = array(); - foreach ( $packed_boxes as $box ) { - $packaging = $box->getBox(); - $items = $box->getItems(); - $total_weight = wc_get_weight( $items->getWeight(), strtolower( get_option( 'woocommerce_weight_unit' ) ), 'g' ); - $volume = wc_get_dimension( $items->getVolume(), strtolower( get_option( 'woocommerce_dimension_unit' ) ), 'mm' ); - $item_count = $items->count(); - $total = 0; - $subtotal = 0; - $products = array(); - $shipping_classes = array(); - - foreach ( $items as $item ) { - $cart_item = $item->getItem(); - $total += $cart_item->get_total(); - $subtotal += $cart_item->get_subtotal(); - $product = $cart_item->get_product(); - - if ( $product && ! array_key_exists( $product->get_id(), $products ) ) { - $products[ $product->get_id() ] = $product; - - if ( ! empty( $product->get_shipping_class_id() ) ) { - $shipping_classes[] = $product->get_shipping_class_id(); - } - } - } - - $total = wc_remove_number_precision( $total ); - $subtotal = wc_remove_number_precision( $subtotal ); - $shipping_classes = array_unique( $shipping_classes ); - $package_data = array_merge( - $cart_data, - array( - 'package_total' => $total, - 'package_subtotal' => $subtotal, - 'package_weight' => $total_weight, - 'package_volume' => $volume, - 'package_item_count' => $item_count, - 'packaging_id' => $packaging->get_id(), - 'package_products' => $products, - 'package_shipping_classes' => $shipping_classes, - ) - ); - - $package_applied_rules = array(); - $applicable_rule_costs = array(); + foreach ( $items as $item ) { + $cart_item = $item->getItem(); + $total += $cart_item->get_total(); + $subtotal += $cart_item->get_subtotal(); + $product = $cart_item->get_product(); - foreach ( array_reverse( $this->get_shipping_rules_by_packaging( $packaging->get_id() ) ) as $rule ) { - $rule = $this->parse_rule( $rule ); - $rule_applies = $this->rule_applies( $rule, $package_data ); + if ( $product && ! array_key_exists( $product->get_id(), $products ) ) { + $products[ $product->get_id() ] = $product; - if ( $rule_applies ) { - $applicable_rule_costs[] = $rule['costs']; - $package_applied_rules[] = $rule['rule_id']; + if ( ! empty( $product->get_shipping_class_id() ) ) { + $shipping_classes[] = $product->get_shipping_class_id(); + } + } } - /** - * In case a free shipping option is detected, stop + reset. - */ - if ( $rule_applies && 0.0 === $rule['costs'] ) { - $applicable_rule_costs = array( - $rule['costs'], - ); - - $package_applied_rules = array( - $rule['rule_id'], - ); + $total = wc_remove_number_precision( $total ); + $subtotal = wc_remove_number_precision( $subtotal ); + $shipping_classes = array_unique( $shipping_classes ); + $package_data = array_merge( + $cart_data, + array( + 'package_total' => $total, + 'package_subtotal' => $subtotal, + 'package_weight' => $total_weight, + 'package_volume' => $volume, + 'package_item_count' => $item_count, + 'packaging_id' => $packaging->get_id(), + 'package_products' => $products, + 'package_shipping_classes' => $shipping_classes, + ) + ); - break; - } - } + $package_applied_rules = array(); + $applicable_rule_costs = array(); - /** - * In case no applicable rule has been found, parse fallback rules. - */ - if ( empty( $package_applied_rules ) ) { - foreach ( array_reverse( $this->get_fallback_shipping_rules() ) as $rule ) { + foreach ( array_reverse( $this->get_shipping_rules_by_packaging( $packaging->get_id() ) ) as $rule ) { $rule = $this->parse_rule( $rule ); $rule_applies = $this->rule_applies( $rule, $package_data ); @@ -651,87 +627,181 @@ public function calculate_shipping( $package = array() ) { break; } } - } - if ( ! empty( $package_applied_rules ) ) { - $applicable_rules_total_cost = 0.0; + /** + * In case no applicable rule has been found, parse fallback rules. + */ + if ( empty( $package_applied_rules ) ) { + foreach ( array_reverse( $this->get_fallback_shipping_rules() ) as $rule ) { + $rule = $this->parse_rule( $rule ); + $rule_applies = $this->rule_applies( $rule, $package_data ); - if ( 'sum' === $multiple_rules_calculation_mode ) { - $applicable_rules_total_cost = array_sum( $applicable_rule_costs ); - } elseif ( 'min' === $multiple_rules_calculation_mode ) { - $applicable_rules_total_cost = min( $applicable_rule_costs ); - } elseif ( 'max' === $multiple_rules_calculation_mode ) { - $applicable_rules_total_cost = max( $applicable_rule_costs ); - } + if ( $rule_applies ) { + $applicable_rule_costs[] = $rule['costs']; + $package_applied_rules[] = $rule['rule_id']; + } - if ( 'min' === $cost_calculation_mode ) { - if ( $applicable_rules_total_cost <= $total_cost || 0.0 === $total_cost ) { - $total_cost = $applicable_rules_total_cost; - } - } elseif ( 'max' === $cost_calculation_mode ) { - if ( $applicable_rules_total_cost >= $total_cost ) { - $total_cost = $applicable_rules_total_cost; + /** + * In case a free shipping option is detected, stop + reset. + */ + if ( $rule_applies && 0.0 === $rule['costs'] ) { + $applicable_rule_costs = array( + $rule['costs'], + ); + + $package_applied_rules = array( + $rule['rule_id'], + ); + + break; + } } - } else { - $total_cost += $applicable_rules_total_cost; } - /** - * Build an item map which contains a map of the cart items - * included within the package. - */ - $item_map = array(); - $weight = 0.0; + if ( ! empty( $package_applied_rules ) ) { + $applicable_rules_total_cost = 0.0; - foreach ( $items as $item ) { - $cart_item_wrapper = $item->getItem(); - $product = $cart_item_wrapper->get_product(); - $product_key = $product->get_parent_id() . '_' . $product->get_id(); - $weight += $cart_item_wrapper->getWeight(); + if ( 'sum' === $multiple_rules_calculation_mode ) { + $applicable_rules_total_cost = array_sum( $applicable_rule_costs ); + } elseif ( 'min' === $multiple_rules_calculation_mode ) { + $applicable_rules_total_cost = min( $applicable_rule_costs ); + } elseif ( 'max' === $multiple_rules_calculation_mode ) { + $applicable_rules_total_cost = max( $applicable_rule_costs ); + } - if ( array_key_exists( $product_key, $item_map ) ) { - $item_map[ $product_key ]++; + if ( 'min' === $cost_calculation_mode ) { + if ( $applicable_rules_total_cost <= $total_cost || 0.0 === $total_cost ) { + $total_cost = $applicable_rules_total_cost; + } + } elseif ( 'max' === $cost_calculation_mode ) { + if ( $applicable_rules_total_cost >= $total_cost ) { + $total_cost = $applicable_rules_total_cost; + } } else { - $item_map[ $product_key ] = 1; + $total_cost += $applicable_rules_total_cost; } - if ( array_key_exists( $product_key, $total_packed_item_map ) ) { - $total_packed_item_map[ $product_key ]++; - } else { - $total_packed_item_map[ $product_key ] = 1; + /** + * Build an item map which contains a map of the cart items + * included within the package. + */ + $item_map = array(); + + foreach ( $items as $item ) { + $cart_item_wrapper = $item->getItem(); + $product = $cart_item_wrapper->get_product(); + $product_key = $product->get_parent_id() . '_' . $product->get_id(); + + if ( array_key_exists( $product_key, $item_map ) ) { + $item_map[ $product_key ]++; + } else { + $item_map[ $product_key ] = 1; + } + + if ( array_key_exists( $product_key, $total_packed_item_map ) ) { + $total_packed_item_map[ $product_key ]++; + } else { + $total_packed_item_map[ $product_key ] = 1; + } + + $total_packed_items++; } - $total_packed_items++; + $applied_rules[] = array( + 'packaging_id' => $packaging->get_id(), + 'rules' => $package_applied_rules, + 'items' => $item_map, + ); + + $rule_ids = array_unique( array_merge( $rule_ids, $package_applied_rules ) ); + $packaging_ids = array_unique( array_merge( $packaging_ids, array( $packaging->get_id() ) ) ); } + } - $applied_rules[] = array( - 'packaging_id' => $packaging->get_id(), - 'rules' => $package_applied_rules, - 'items' => $item_map, - 'weight' => $weight + $packaging->getEmptyWeight(), + if ( ! empty( $applied_rules ) ) { + if ( $is_debug_mode ) { + $package_count = 0; + + foreach ( $applied_rules as $applied_rule ) { + if ( $packaging = wc_gzd_get_packaging( $applied_rule['packaging_id'] ) ) { + $package_count++; + $debug_notices[] = sprintf( _x( '## Package %1$d/%2$d: %3$s: ', 'shipments', 'woocommerce-germanized-shipments' ), $package_count, count( $applied_rules ), $packaging->get_title() ); + + foreach ( $applied_rule['rules'] as $rule ) { + if ( $the_rule = $this->get_shipping_rule_by_id( $rule, $applied_rule['packaging_id'] ) ) { + $debug_notices[] = sprintf( _x( 'Rule %1$d: %2$s', 'shipments', 'woocommerce-germanized-shipments' ), $rule, wc_price( $the_rule['costs'] ) ); + } + } + + foreach ( $applied_rule['items'] as $item_product_key => $quantity ) { + $product_ids = explode( '_', $item_product_key ); + $product_title = $product_ids[0]; + + if ( $product = wc_get_product( $product_ids[1] ) ) { + $product_title = $product->get_title(); + } + + $product_desc = ! empty( $product_ids[0] ) ? sprintf( _x( '%1$s (Parent: %2$s)', 'shipments', 'woocommerce-germanized-shipments' ), $product_title, $product_ids[0] ) : $product_title; + $debug_notices[] = sprintf( _x( '%1$s x %2$s', 'shipments', 'woocommerce-germanized-shipments' ), $quantity, $product_desc ); + } + } + } + + $debug_notices[] = sprintf( _x( '## Total: %1$s (%2$s, %3$s)', 'shipments', 'woocommerce-germanized-shipments' ), wc_price( $total_cost ), $cost_calculation_mode, $multiple_rules_calculation_mode ); + } + + $this->add_rate( + array( + 'cost' => $total_cost, + 'label' => $this->get_rate_label( $total_cost ), + 'package' => $package, + 'meta_data' => array( + '_packed_items' => $total_packed_items, + '_packed_item_map' => $total_packed_item_map, + '_packaging_ids' => $packaging_ids, + '_rule_ids' => $rule_ids, + '_packages' => $applied_rules, + ), + ) ); + } elseif ( $is_debug_mode ) { + $debug_notices[] = _x( 'None of the available rules applied.', 'shipments', 'woocommerce-germanized-shipments' ); + } + } elseif ( $is_debug_mode ) { + foreach ( $unpacked_items as $item ) { + $product_desc = $item->get_id(); - $rule_ids = array_unique( array_merge( $rule_ids, $package_applied_rules ) ); - $packaging_ids = array_unique( array_merge( $packaging_ids, array( $packaging->get_id() ) ) ); + if ( $product = $item->get_product() ) { + $product_desc = $product->get_title(); + } + + $debug_notices[] = sprintf( _x( '%1$s does not fit the available packaging options', 'shipments', 'woocommerce-germanized-shipments' ), $product_desc ); } } } - if ( ! empty( $applied_rules ) ) { - $this->add_rate( - array( - 'cost' => $total_cost, - 'label' => $this->get_rate_label( $total_cost ), - 'package' => $package, - 'meta_data' => array( - '_packed_items' => $total_packed_items, - '_packed_item_map' => $total_packed_item_map, - '_packaging_ids' => $packaging_ids, - '_rule_ids' => $rule_ids, - '_packages' => $applied_rules, - ), - ) + if ( $is_debug_mode && ! Package::is_constant_defined( 'WOOCOMMERCE_CHECKOUT' ) && ! Package::is_constant_defined( 'WC_DOING_AJAX' ) && ! empty( $debug_notices ) ) { + $the_notice = ''; + $available_box_list = array(); + + foreach ( $available_boxes as $box ) { + $available_box_list[] = $box->get_packaging()->get_title(); + } + + $general_debug_notices = array( + sprintf( _x( '### Debug information for %1$s:', 'shipments', 'woocommerce-germanized-shipments' ), $this->get_title() ), + sprintf( _x( 'Available packaging options: %1$s', 'shipments', 'woocommerce-germanized-shipments' ), implode( ', ', $available_box_list ) ), ); + + $debug_notices = array_merge( $general_debug_notices, $debug_notices ); + + foreach ( $debug_notices as $notice ) { + $the_notice .= $notice . '
'; + } + + if ( ! wc_has_notice( $the_notice ) ) { + wc_add_notice( $the_notice ); + } } } @@ -1100,7 +1170,7 @@ protected function generate_shipping_rules_html( $option_name, $option ) { - +