BlockEditing
3 days ago
Convert
3 days ago
Duplicate
3 days ago
Management
3 days ago
Timestamps
3 days ago
Tracker
3 days ago
views
3 days ago
CommonMethodSettings.php
3 days ago
FreeShippingCalculator.php
3 days ago
FreeShippingThresholdRuleValidator.php
3 days ago
MethodDescription.php
3 days ago
MethodLogo.php
3 days ago
MethodLogoCheckoutBlocksAssets.php
3 days ago
MethodLogoSettingsField.php
3 days ago
MethodSettings.php
3 days ago
MethodTitle.php
3 days ago
RateCalculator.php
3 days ago
RateCalculatorFactory.php
3 days ago
SettingsDisplayPreparer.php
3 days ago
SettingsProcessor.php
3 days ago
SingleMethodSettings.php
3 days ago
RateCalculator.php
395 lines
| 1 | <?php |
| 2 | /** |
| 3 | * Class RateCalculator |
| 4 | * |
| 5 | * @package WPDesk\FS\TableRate\ShippingMethod |
| 6 | */ |
| 7 | |
| 8 | namespace WPDesk\FS\TableRate\ShippingMethod; |
| 9 | |
| 10 | use FSVendor\WPDesk\Forms\Field; |
| 11 | use FSVendor\WPDesk\FS\TableRate\Settings\CartCalculationOptions; |
| 12 | use FSVendor\WPDesk\FS\TableRate\Settings\MethodSettingsImplementation; |
| 13 | use FSVendor\Psr\Log\LoggerInterface; |
| 14 | use FSVendor\Psr\Log\NullLogger; |
| 15 | use WC_Cart; |
| 16 | use WC_Shipping_Method; |
| 17 | use WPDesk\FS\TableRate\Rule\Condition\Condition; |
| 18 | use WPDesk\FS\TableRate\Rule\Cost\AdditionalCost; |
| 19 | use WPDesk\FS\TableRate\Rule\CostsCalculator; |
| 20 | use WPDesk\FS\TableRate\Rule\ShippingContents\DestinationAddressFactory; |
| 21 | use WPDesk\FS\TableRate\Rule\ShippingContents\ShippingContents; |
| 22 | use WPDesk\FS\TableRate\Rule\ShippingContents\ShippingContentsImplementation; |
| 23 | use WPDesk\FS\TableRate\Rule\SpecialAction\SpecialAction; |
| 24 | use WPDesk_Flexible_Shipping; |
| 25 | |
| 26 | /** |
| 27 | * Can calculate single shipping rate. |
| 28 | */ |
| 29 | class RateCalculator { |
| 30 | |
| 31 | const FS_INTEGRATION = '_fs_integration'; |
| 32 | const FS_METHOD = '_fs_method'; |
| 33 | const DESCRIPTION = 'description'; |
| 34 | const DESCRIPTION_BASE64ENCODED = 'description_base64encoded'; |
| 35 | const METHOD_LOGO_URL = 'method_logo_url'; |
| 36 | const METHOD_LOGO_ALT = 'method_logo_alt'; |
| 37 | |
| 38 | /** |
| 39 | * @var LoggerInterface |
| 40 | */ |
| 41 | private $logger; |
| 42 | |
| 43 | /** |
| 44 | * @var WC_Shipping_Method |
| 45 | */ |
| 46 | private $shipping_method; |
| 47 | |
| 48 | /** |
| 49 | * @var string |
| 50 | */ |
| 51 | private $shop_currency; |
| 52 | |
| 53 | /** |
| 54 | * @var string |
| 55 | */ |
| 56 | private $cart_currency; |
| 57 | |
| 58 | /** |
| 59 | * @var Condition[] |
| 60 | */ |
| 61 | private $available_conditions; |
| 62 | |
| 63 | /** |
| 64 | * @var Field[] |
| 65 | */ |
| 66 | private $cost_fields; |
| 67 | |
| 68 | /** |
| 69 | * @var AdditionalCost |
| 70 | */ |
| 71 | private $available_additional_costs; |
| 72 | |
| 73 | /** |
| 74 | * @var SpecialAction[] |
| 75 | */ |
| 76 | private $available_special_actions; |
| 77 | |
| 78 | /** |
| 79 | * @var int |
| 80 | */ |
| 81 | private $cost_rounding_precision; |
| 82 | |
| 83 | /** |
| 84 | * @var bool |
| 85 | */ |
| 86 | private $prices_includes_tax; |
| 87 | |
| 88 | /** |
| 89 | * @var WC_Cart |
| 90 | */ |
| 91 | private $cart; |
| 92 | |
| 93 | /** |
| 94 | * @var ShippingContents |
| 95 | */ |
| 96 | private $cart_contents; |
| 97 | |
| 98 | /** |
| 99 | * @var array |
| 100 | */ |
| 101 | private $package; |
| 102 | |
| 103 | /** |
| 104 | * @var FreeShippingCalculator |
| 105 | */ |
| 106 | private $free_shipping_calculator; |
| 107 | |
| 108 | /** |
| 109 | * RateCalculator constructor. |
| 110 | * |
| 111 | * @param WC_Shipping_Method $shipping_method . |
| 112 | * @param string $shop_currency . |
| 113 | * @param string $cart_currency . |
| 114 | * @param Condition[] $available_conditions . |
| 115 | * @param Field[] $cost_fields . |
| 116 | * @param AdditionalCost[] $available_additional_costs . |
| 117 | * @param SpecialAction[] $available_special_actions . |
| 118 | * @param int $cost_rounding_precision . |
| 119 | * @param bool $prices_includes_tax . |
| 120 | * @param WC_Cart $cart . |
| 121 | * @param ShippingContents $cart_contents . |
| 122 | * @param array $package . |
| 123 | * @param FreeShippingCalculator $free_shipping_calculator . |
| 124 | */ |
| 125 | public function __construct( |
| 126 | WC_Shipping_Method $shipping_method, |
| 127 | $shop_currency, |
| 128 | $cart_currency, |
| 129 | array $available_conditions, |
| 130 | array $cost_fields, |
| 131 | array $available_additional_costs, |
| 132 | array $available_special_actions, |
| 133 | $cost_rounding_precision, |
| 134 | $prices_includes_tax, |
| 135 | WC_Cart $cart, |
| 136 | ShippingContents $cart_contents, |
| 137 | array $package, |
| 138 | FreeShippingCalculator $free_shipping_calculator |
| 139 | ) { |
| 140 | $this->shipping_method = $shipping_method; |
| 141 | $this->shop_currency = $shop_currency; |
| 142 | $this->cart_currency = $cart_currency; |
| 143 | $this->available_conditions = $available_conditions; |
| 144 | $this->cost_fields = $cost_fields; |
| 145 | $this->available_additional_costs = $available_additional_costs; |
| 146 | $this->available_special_actions = $available_special_actions; |
| 147 | $this->cost_rounding_precision = $cost_rounding_precision; |
| 148 | $this->prices_includes_tax = $prices_includes_tax; |
| 149 | $this->cart = $cart; |
| 150 | $this->cart_contents = $cart_contents; |
| 151 | $this->package = $package; |
| 152 | $this->free_shipping_calculator = $free_shipping_calculator; |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * @param LoggerInterface $logger . |
| 157 | */ |
| 158 | public function set_logger( LoggerInterface $logger ) { |
| 159 | $this->logger = $logger; |
| 160 | } |
| 161 | |
| 162 | /** |
| 163 | * @return LoggerInterface |
| 164 | */ |
| 165 | public function get_logger() { |
| 166 | if ( null === $this->logger ) { |
| 167 | $this->logger = new NullLogger(); |
| 168 | } |
| 169 | |
| 170 | return $this->logger; |
| 171 | } |
| 172 | |
| 173 | /** |
| 174 | * @param MethodSettingsImplementation $method_settings . |
| 175 | * @param string $rate_id . |
| 176 | * @param bool $user_logged_in . |
| 177 | * |
| 178 | * @return array |
| 179 | */ |
| 180 | public function calculate_rate( MethodSettingsImplementation $method_settings, $rate_id, $user_logged_in ) { |
| 181 | $logger = $this->get_logger(); |
| 182 | |
| 183 | $logger->debug( $method_settings->format_for_log(), $logger->get_configuration_section_context() ); |
| 184 | |
| 185 | $add_method = true; |
| 186 | if ( 'yes' === $method_settings->get_visibility() && ! $user_logged_in ) { |
| 187 | $logger->debug( __( 'Method available only for logged in users, but user is not logged in.', 'flexible-shipping' ), $logger->get_results_context() ); |
| 188 | $add_method = false; |
| 189 | } |
| 190 | |
| 191 | if ( $add_method ) { |
| 192 | if ( $this->shop_currency !== $this->cart_currency ) { |
| 193 | // Translators: shop currency. |
| 194 | $logger->debug( sprintf( __( 'Shop currency: %1$s', 'flexible-shipping' ), $this->shop_currency ), $logger->get_input_data_context() ); |
| 195 | // Translators: cart currency. |
| 196 | $logger->debug( sprintf( __( 'Cart currency: %1$s', 'flexible-shipping' ), $this->cart_currency ), $logger->get_input_data_context() ); |
| 197 | } |
| 198 | |
| 199 | if ( CartCalculationOptions::PACKAGE === $method_settings->get_cart_calculation() ) { |
| 200 | $shipping_contents = new ShippingContentsImplementation( |
| 201 | $this->package['contents'], |
| 202 | $this->prices_includes_tax, |
| 203 | $this->cost_rounding_precision, |
| 204 | DestinationAddressFactory::create_from_package_destination( $this->package['destination'] ), |
| 205 | get_woocommerce_currency() |
| 206 | ); |
| 207 | } else { |
| 208 | $shipping_contents = $this->cart_contents; |
| 209 | } |
| 210 | |
| 211 | /** |
| 212 | * @return ShippingContents |
| 213 | */ |
| 214 | $shipping_contents = apply_filters( 'flexible_shipping_shipping_contents', $shipping_contents, $method_settings->get_raw_settings(), $this->cart, $this->package ); |
| 215 | |
| 216 | if ( ! $shipping_contents instanceof ShippingContents || empty( $shipping_contents->get_contents() ) ) { |
| 217 | $logger->debug( __( 'Empty contents', 'flexible-shipping' ) ); |
| 218 | |
| 219 | return []; |
| 220 | } |
| 221 | |
| 222 | do_action( 'flexible-shipping/rate-calculator/before', $shipping_contents, $method_settings, $logger ); |
| 223 | |
| 224 | // Translators: contents value. |
| 225 | $logger->debug( sprintf( __( 'Contents value: %1$s %2$s', 'flexible-shipping' ), $shipping_contents->get_contents_cost(), $shipping_contents->get_currency() ), $logger->get_input_data_context() ); |
| 226 | // Translators: contents weight. |
| 227 | $logger->debug( sprintf( __( 'Contents weight: %1$s', 'flexible-shipping' ), wc_format_weight( $shipping_contents->get_contents_weight() ) ), $logger->get_input_data_context() ); |
| 228 | |
| 229 | $rate = $this->calculate_rate_for_rates( $method_settings, $shipping_contents, $rate_id, $logger ); |
| 230 | |
| 231 | do_action( 'flexible-shipping/rate-calculator/log', $logger ); |
| 232 | } else { |
| 233 | $rate = []; |
| 234 | } |
| 235 | |
| 236 | return $rate; |
| 237 | } |
| 238 | |
| 239 | /** |
| 240 | * @param MethodSettingsImplementation $method_settings . |
| 241 | * @param ShippingContents $shipping_contents . |
| 242 | * @param string $rate_id . |
| 243 | * @param LoggerInterface $logger . |
| 244 | * |
| 245 | * @return array |
| 246 | */ |
| 247 | private function calculate_rate_for_rates( MethodSettingsImplementation $method_settings, ShippingContents $shipping_contents, $rate_id, LoggerInterface $logger ) { |
| 248 | $cost_calculator = new CostsCalculator( |
| 249 | $method_settings, |
| 250 | $shipping_contents, |
| 251 | $this->available_conditions, |
| 252 | $this->cost_fields, |
| 253 | $this->available_additional_costs, |
| 254 | $this->available_special_actions, |
| 255 | $this->cost_rounding_precision, |
| 256 | $this->shop_currency, |
| 257 | $logger |
| 258 | ); |
| 259 | |
| 260 | $cost_calculator->process_rules(); |
| 261 | $add_method = $cost_calculator->is_triggered(); |
| 262 | |
| 263 | // Translators: add method. |
| 264 | $logger->debug( sprintf( __( 'Used and displayed in the cart/checkout: %1$s', 'flexible-shipping' ), $add_method ? __( 'yes', 'flexible-shipping' ) : __( 'no', 'flexible-shipping' ) ), $logger->get_results_context() ); |
| 265 | $add_method_before_filters = $add_method; |
| 266 | |
| 267 | $add_method = apply_filters( 'flexible_shipping_add_method', $add_method, $method_settings->get_raw_settings(), $this->package, $this->shipping_method ); |
| 268 | if ( $add_method_before_filters !== $add_method ) { |
| 269 | // Translators: add method. |
| 270 | $logger->debug( sprintf( __( 'Used and displayed in the cart/checkout after filters: %1$s', 'flexible-shipping' ), $add_method ? __( 'yes', 'flexible-shipping' ) : __( 'no', 'flexible-shipping' ) ), $logger->get_results_context() ); |
| 271 | } |
| 272 | |
| 273 | if ( $add_method ) { |
| 274 | $cost = $this->set_zero_cost_if_negative( $cost_calculator->get_calculated_cost() ); |
| 275 | |
| 276 | // Translators: cost, currency. |
| 277 | $logger->debug( sprintf( __( 'Calculated shipping cost: %1$s %2$s', 'flexible-shipping' ), $cost, $this->shop_currency ), $logger->get_results_context() ); |
| 278 | |
| 279 | $is_free_shipping = $this->free_shipping_calculator->is_free_shipping( $method_settings, $this->cart_contents->get_contents_cost() ); |
| 280 | // Translators: free shipping. |
| 281 | $logger->debug( sprintf( __( 'Free shipping: %1$s', 'flexible-shipping' ), $is_free_shipping ? __( 'yes', 'flexible-shipping' ) : __( 'no', 'flexible-shipping' ) ), $logger->get_results_context() ); |
| 282 | |
| 283 | if ( $is_free_shipping ) { |
| 284 | $cost = 0.0; |
| 285 | // Translators: shipping cost after free shipping. |
| 286 | $logger->debug( sprintf( __( 'Shipping cost after free shipping applied: %1$s', 'flexible-shipping' ), $cost ), $logger->get_results_context() ); |
| 287 | } |
| 288 | |
| 289 | // Translators: method id. |
| 290 | $logger->debug( sprintf( __( 'Shipping method ID: %1$s', 'flexible-shipping' ), $rate_id ), $logger->get_results_context() ); |
| 291 | |
| 292 | $method_title = $this->get_single_method_title( $method_settings->get_raw_settings(), $cost ); |
| 293 | // Translators: method title. |
| 294 | $logger->debug( sprintf( __( 'Shipping method title: %1$s', 'flexible-shipping' ), $method_title ), $logger->get_results_context() ); |
| 295 | |
| 296 | $rate = [ |
| 297 | 'id' => $rate_id, |
| 298 | 'label' => $method_title, |
| 299 | 'cost' => $cost, |
| 300 | 'package' => $this->package, |
| 301 | 'meta_data' => $this->prepare_meta_data( $method_settings ), |
| 302 | ]; |
| 303 | } else { |
| 304 | $rate = []; |
| 305 | } |
| 306 | |
| 307 | return $rate; |
| 308 | } |
| 309 | |
| 310 | /** |
| 311 | * @param float $cost . |
| 312 | * |
| 313 | * @return float |
| 314 | */ |
| 315 | private function set_zero_cost_if_negative( $cost ) { |
| 316 | $allow_negative_costs = (bool) apply_filters( 'flexible-shipping/shipping-method/allow-negative-costs', false ); |
| 317 | |
| 318 | if ( ! $allow_negative_costs && 0.0 > (float) $cost ) { |
| 319 | $cost = 0.0; |
| 320 | } |
| 321 | |
| 322 | return (float) $cost; |
| 323 | } |
| 324 | |
| 325 | /** |
| 326 | * @param MethodSettingsImplementation $method_settings . |
| 327 | * |
| 328 | * @return array |
| 329 | */ |
| 330 | private function prepare_meta_data( MethodSettingsImplementation $method_settings ) { |
| 331 | $description = $this->get_method_description( $method_settings ); |
| 332 | $logo_data = MethodLogo::get_logo_data( $method_settings->get_raw_settings() ); |
| 333 | |
| 334 | $meta_data = [ |
| 335 | WPDesk_Flexible_Shipping::META_DEFAULT => $method_settings->get_default(), |
| 336 | self::FS_METHOD => $method_settings->get_raw_settings(), |
| 337 | self::FS_INTEGRATION => $method_settings->get_integration(), |
| 338 | self::DESCRIPTION => $description, |
| 339 | ]; |
| 340 | |
| 341 | if ( ! apply_filters( 'flexible-shipping/order-meta-data/keep-method-rules', false, $meta_data ) ) { |
| 342 | unset( $meta_data[ self::FS_METHOD ][ CommonMethodSettings::METHOD_RULES ] ); |
| 343 | } |
| 344 | |
| 345 | if ( esc_html( $description ) !== $description ) { |
| 346 | $meta_data[ self::DESCRIPTION_BASE64ENCODED ] = base64_encode( $description ); |
| 347 | } |
| 348 | |
| 349 | if ( '' !== $logo_data['url'] ) { |
| 350 | $meta_data[ self::METHOD_LOGO_URL ] = $logo_data['url']; |
| 351 | $meta_data[ self::METHOD_LOGO_ALT ] = $logo_data['alt']; |
| 352 | } |
| 353 | |
| 354 | /** |
| 355 | * Rate metadata. |
| 356 | * |
| 357 | * @param array $meta_data . |
| 358 | * @param array $method_settings . |
| 359 | * |
| 360 | * @return array |
| 361 | */ |
| 362 | return apply_filters( 'flexible-shipping/rate/meta_data', $meta_data, $method_settings->get_raw_settings() ); |
| 363 | } |
| 364 | |
| 365 | /** |
| 366 | * @param MethodSettingsImplementation $method_settings . |
| 367 | * |
| 368 | * @return string |
| 369 | */ |
| 370 | private function get_method_description( MethodSettingsImplementation $method_settings ) { |
| 371 | return wp_kses_post( wpdesk__( $method_settings->get_description(), 'flexible-shipping' ) ); |
| 372 | } |
| 373 | |
| 374 | /** |
| 375 | * @param array $shipping_method . |
| 376 | * @param float $cost . |
| 377 | * |
| 378 | * @return mixed|string|void |
| 379 | */ |
| 380 | private function get_single_method_title( $shipping_method, $cost ) { |
| 381 | $method_title = wpdesk__( $shipping_method['method_title'], 'flexible-shipping' ); |
| 382 | |
| 383 | if ( 0.0 >= (float) $cost ) { |
| 384 | if ( ! isset( $shipping_method['method_free_shipping_label'] ) ) { |
| 385 | $shipping_method['method_free_shipping_label'] = __( 'Free', 'flexible-shipping' ); |
| 386 | } |
| 387 | if ( '' !== $shipping_method['method_free_shipping_label'] ) { |
| 388 | $method_title .= ' (' . wpdesk__( $shipping_method['method_free_shipping_label'], 'flexible-shipping' ) . ')'; |
| 389 | } |
| 390 | } |
| 391 | |
| 392 | return $method_title; |
| 393 | } |
| 394 | } |
| 395 |