class-wc-shipping-legacy-flat-rate.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. <?php
  2. /**
  3. * Class WC_Shipping_Legacy_Flat_Rate file.
  4. *
  5. * @package WooCommerce\Shipping
  6. */
  7. if ( ! defined( 'ABSPATH' ) ) {
  8. exit;
  9. }
  10. /**
  11. * Flat Rate Shipping Method.
  12. *
  13. * This class is here for backwards compatibility for methods existing before zones existed.
  14. *
  15. * @deprecated 2.6.0
  16. * @version 2.4.0
  17. * @package WooCommerce/Classes/Shipping
  18. */
  19. class WC_Shipping_Legacy_Flat_Rate extends WC_Shipping_Method {
  20. /**
  21. * Cost passed to [fee] shortcode.
  22. *
  23. * @var string
  24. */
  25. protected $fee_cost = '';
  26. /**
  27. * Constructor.
  28. */
  29. public function __construct() {
  30. $this->id = 'legacy_flat_rate';
  31. $this->method_title = __( 'Flat rate (legacy)', 'woocommerce' );
  32. /* translators: %s: Admin shipping settings URL */
  33. $this->method_description = '<strong>' . sprintf( __( 'This method is deprecated in 2.6.0 and will be removed in future versions - we recommend disabling it and instead setting up a new rate within your <a href="%s">Shipping zones</a>.', 'woocommerce' ), admin_url( 'admin.php?page=wc-settings&tab=shipping' ) ) . '</strong>';
  34. $this->init();
  35. add_action( 'woocommerce_update_options_shipping_' . $this->id, array( $this, 'process_admin_options' ) );
  36. add_action( 'woocommerce_flat_rate_shipping_add_rate', array( $this, 'calculate_extra_shipping' ), 10, 2 );
  37. }
  38. /**
  39. * Process and redirect if disabled.
  40. */
  41. public function process_admin_options() {
  42. parent::process_admin_options();
  43. if ( 'no' === $this->settings['enabled'] ) {
  44. wp_redirect( admin_url( 'admin.php?page=wc-settings&tab=shipping&section=options' ) );
  45. exit;
  46. }
  47. }
  48. /**
  49. * Return the name of the option in the WP DB.
  50. *
  51. * @since 2.6.0
  52. * @return string
  53. */
  54. public function get_option_key() {
  55. return $this->plugin_id . 'flat_rate_settings';
  56. }
  57. /**
  58. * Init function.
  59. */
  60. public function init() {
  61. // Load the settings.
  62. $this->init_form_fields();
  63. $this->init_settings();
  64. // Define user set variables.
  65. $this->title = $this->get_option( 'title' );
  66. $this->availability = $this->get_option( 'availability' );
  67. $this->countries = $this->get_option( 'countries' );
  68. $this->tax_status = $this->get_option( 'tax_status' );
  69. $this->cost = $this->get_option( 'cost' );
  70. $this->type = $this->get_option( 'type', 'class' );
  71. $this->options = $this->get_option( 'options', false ); // @deprecated in 2.4.0
  72. }
  73. /**
  74. * Initialise Settings Form Fields.
  75. */
  76. public function init_form_fields() {
  77. $this->form_fields = include 'includes/settings-flat-rate.php';
  78. }
  79. /**
  80. * Evaluate a cost from a sum/string.
  81. *
  82. * @param string $sum Sum to evaluate.
  83. * @param array $args Arguments.
  84. * @return string
  85. */
  86. protected function evaluate_cost( $sum, $args = array() ) {
  87. include_once WC()->plugin_path() . '/includes/libraries/class-wc-eval-math.php';
  88. $locale = localeconv();
  89. $decimals = array( wc_get_price_decimal_separator(), $locale['decimal_point'], $locale['mon_decimal_point'] );
  90. $this->fee_cost = $args['cost'];
  91. // Expand shortcodes.
  92. add_shortcode( 'fee', array( $this, 'fee' ) );
  93. $sum = do_shortcode(
  94. str_replace(
  95. array(
  96. '[qty]',
  97. '[cost]',
  98. ),
  99. array(
  100. $args['qty'],
  101. $args['cost'],
  102. ),
  103. $sum
  104. )
  105. );
  106. remove_shortcode( 'fee', array( $this, 'fee' ) );
  107. // Remove whitespace from string.
  108. $sum = preg_replace( '/\s+/', '', $sum );
  109. // Remove locale from string.
  110. $sum = str_replace( $decimals, '.', $sum );
  111. // Trim invalid start/end characters.
  112. $sum = rtrim( ltrim( $sum, "\t\n\r\0\x0B+*/" ), "\t\n\r\0\x0B+-*/" );
  113. // Do the math.
  114. return $sum ? WC_Eval_Math::evaluate( $sum ) : 0;
  115. }
  116. /**
  117. * Work out fee (shortcode).
  118. *
  119. * @param array $atts Shortcode attributes.
  120. * @return string
  121. */
  122. public function fee( $atts ) {
  123. $atts = shortcode_atts(
  124. array(
  125. 'percent' => '',
  126. 'min_fee' => '',
  127. ), $atts, 'fee'
  128. );
  129. $calculated_fee = 0;
  130. if ( $atts['percent'] ) {
  131. $calculated_fee = $this->fee_cost * ( floatval( $atts['percent'] ) / 100 );
  132. }
  133. if ( $atts['min_fee'] && $calculated_fee < $atts['min_fee'] ) {
  134. $calculated_fee = $atts['min_fee'];
  135. }
  136. return $calculated_fee;
  137. }
  138. /**
  139. * Calculate shipping.
  140. *
  141. * @param array $package (default: array()).
  142. */
  143. public function calculate_shipping( $package = array() ) {
  144. $rate = array(
  145. 'id' => $this->id,
  146. 'label' => $this->title,
  147. 'cost' => 0,
  148. 'package' => $package,
  149. );
  150. // Calculate the costs.
  151. $has_costs = false; // True when a cost is set. False if all costs are blank strings.
  152. $cost = $this->get_option( 'cost' );
  153. if ( '' !== $cost ) {
  154. $has_costs = true;
  155. $rate['cost'] = $this->evaluate_cost(
  156. $cost, array(
  157. 'qty' => $this->get_package_item_qty( $package ),
  158. 'cost' => $package['contents_cost'],
  159. )
  160. );
  161. }
  162. // Add shipping class costs.
  163. $found_shipping_classes = $this->find_shipping_classes( $package );
  164. $highest_class_cost = 0;
  165. foreach ( $found_shipping_classes as $shipping_class => $products ) {
  166. // Also handles BW compatibility when slugs were used instead of ids.
  167. $shipping_class_term = get_term_by( 'slug', $shipping_class, 'product_shipping_class' );
  168. $class_cost_string = $shipping_class_term && $shipping_class_term->term_id ? $this->get_option( 'class_cost_' . $shipping_class_term->term_id, $this->get_option( 'class_cost_' . $shipping_class, '' ) ) : $this->get_option( 'no_class_cost', '' );
  169. if ( '' === $class_cost_string ) {
  170. continue;
  171. }
  172. $has_costs = true;
  173. $class_cost = $this->evaluate_cost(
  174. $class_cost_string, array(
  175. 'qty' => array_sum( wp_list_pluck( $products, 'quantity' ) ),
  176. 'cost' => array_sum( wp_list_pluck( $products, 'line_total' ) ),
  177. )
  178. );
  179. if ( 'class' === $this->type ) {
  180. $rate['cost'] += $class_cost;
  181. } else {
  182. $highest_class_cost = $class_cost > $highest_class_cost ? $class_cost : $highest_class_cost;
  183. }
  184. }
  185. if ( 'order' === $this->type && $highest_class_cost ) {
  186. $rate['cost'] += $highest_class_cost;
  187. }
  188. $rate['package'] = $package;
  189. // Add the rate.
  190. if ( $has_costs ) {
  191. $this->add_rate( $rate );
  192. }
  193. /**
  194. * Developers can add additional flat rates based on this one via this action since @version 2.4.
  195. *
  196. * Previously there were (overly complex) options to add additional rates however this was not user.
  197. * friendly and goes against what Flat Rate Shipping was originally intended for.
  198. *
  199. * This example shows how you can add an extra rate based on this flat rate via custom function:
  200. *
  201. * add_action( 'woocommerce_flat_rate_shipping_add_rate', 'add_another_custom_flat_rate', 10, 2 );
  202. *
  203. * function add_another_custom_flat_rate( $method, $rate ) {
  204. * $new_rate = $rate;
  205. * $new_rate['id'] .= ':' . 'custom_rate_name'; // Append a custom ID.
  206. * $new_rate['label'] = 'Rushed Shipping'; // Rename to 'Rushed Shipping'.
  207. * $new_rate['cost'] += 2; // Add $2 to the cost.
  208. *
  209. * // Add it to WC.
  210. * $method->add_rate( $new_rate );
  211. * }.
  212. */
  213. do_action( 'woocommerce_flat_rate_shipping_add_rate', $this, $rate );
  214. }
  215. /**
  216. * Get items in package.
  217. *
  218. * @param array $package Package information.
  219. * @return int
  220. */
  221. public function get_package_item_qty( $package ) {
  222. $total_quantity = 0;
  223. foreach ( $package['contents'] as $item_id => $values ) {
  224. if ( $values['quantity'] > 0 && $values['data']->needs_shipping() ) {
  225. $total_quantity += $values['quantity'];
  226. }
  227. }
  228. return $total_quantity;
  229. }
  230. /**
  231. * Finds and returns shipping classes and the products with said class.
  232. *
  233. * @param mixed $package Package information.
  234. * @return array
  235. */
  236. public function find_shipping_classes( $package ) {
  237. $found_shipping_classes = array();
  238. foreach ( $package['contents'] as $item_id => $values ) {
  239. if ( $values['data']->needs_shipping() ) {
  240. $found_class = $values['data']->get_shipping_class();
  241. if ( ! isset( $found_shipping_classes[ $found_class ] ) ) {
  242. $found_shipping_classes[ $found_class ] = array();
  243. }
  244. $found_shipping_classes[ $found_class ][ $item_id ] = $values;
  245. }
  246. }
  247. return $found_shipping_classes;
  248. }
  249. /**
  250. * Adds extra calculated flat rates.
  251. *
  252. * @deprecated 2.4.0
  253. *
  254. * Additional rates defined like this:
  255. * Option Name | Additional Cost [+- Percents%] | Per Cost Type (order, class, or item).
  256. *
  257. * @param null $method Deprecated.
  258. * @param array $rate Rate information.
  259. */
  260. public function calculate_extra_shipping( $method, $rate ) {
  261. if ( $this->options ) {
  262. $options = array_filter( (array) explode( "\n", $this->options ) );
  263. foreach ( $options as $option ) {
  264. $this_option = array_map( 'trim', explode( WC_DELIMITER, $option ) );
  265. if ( count( $this_option ) !== 3 ) {
  266. continue;
  267. }
  268. $extra_rate = $rate;
  269. $extra_rate['id'] = $this->id . ':' . urldecode( sanitize_title( $this_option[0] ) );
  270. $extra_rate['label'] = $this_option[0];
  271. $extra_cost = $this->get_extra_cost( $this_option[1], $this_option[2], $rate['package'] );
  272. if ( is_array( $extra_rate['cost'] ) ) {
  273. $extra_rate['cost']['order'] = $extra_rate['cost']['order'] + $extra_cost;
  274. } else {
  275. $extra_rate['cost'] += $extra_cost;
  276. }
  277. $this->add_rate( $extra_rate );
  278. }
  279. }
  280. }
  281. /**
  282. * Calculate the percentage adjustment for each shipping rate.
  283. *
  284. * @deprecated 2.4.0
  285. * @param float $cost Cost.
  286. * @param float $percent_adjustment Percent adjusment.
  287. * @param string $percent_operator Percent operator.
  288. * @param float $base_price Base price.
  289. * @return float
  290. */
  291. public function calc_percentage_adjustment( $cost, $percent_adjustment, $percent_operator, $base_price ) {
  292. if ( '+' === $percent_operator ) {
  293. $cost += $percent_adjustment * $base_price;
  294. } else {
  295. $cost -= $percent_adjustment * $base_price;
  296. }
  297. return $cost;
  298. }
  299. /**
  300. * Get extra cost.
  301. *
  302. * @deprecated 2.4.0
  303. * @param string $cost_string Cost string.
  304. * @param string $type Type.
  305. * @param array $package Package information.
  306. * @return float
  307. */
  308. public function get_extra_cost( $cost_string, $type, $package ) {
  309. $cost = $cost_string;
  310. $cost_percent = false;
  311. // @codingStandardsIgnoreStart
  312. $pattern =
  313. '/' . // Start regex.
  314. '(\d+\.?\d*)' . // Capture digits, optionally capture a `.` and more digits.
  315. '\s*' . // Match whitespace.
  316. '(\+|-)' . // Capture the operand.
  317. '\s*' . // Match whitespace.
  318. '(\d+\.?\d*)' . // Capture digits, optionally capture a `.` and more digits.
  319. '\%/'; // Match the percent sign & end regex.
  320. // @codingStandardsIgnoreEnd
  321. if ( preg_match( $pattern, $cost_string, $this_cost_matches ) ) {
  322. $cost_operator = $this_cost_matches[2];
  323. $cost_percent = $this_cost_matches[3] / 100;
  324. $cost = $this_cost_matches[1];
  325. }
  326. switch ( $type ) {
  327. case 'class':
  328. $cost = $cost * count( $this->find_shipping_classes( $package ) );
  329. break;
  330. case 'item':
  331. $cost = $cost * $this->get_package_item_qty( $package );
  332. break;
  333. }
  334. if ( $cost_percent ) {
  335. switch ( $type ) {
  336. case 'class':
  337. $shipping_classes = $this->find_shipping_classes( $package );
  338. foreach ( $shipping_classes as $shipping_class => $items ) {
  339. foreach ( $items as $item_id => $values ) {
  340. $cost = $this->calc_percentage_adjustment( $cost, $cost_percent, $cost_operator, $values['line_total'] );
  341. }
  342. }
  343. break;
  344. case 'item':
  345. foreach ( $package['contents'] as $item_id => $values ) {
  346. if ( $values['data']->needs_shipping() ) {
  347. $cost = $this->calc_percentage_adjustment( $cost, $cost_percent, $cost_operator, $values['line_total'] );
  348. }
  349. }
  350. break;
  351. case 'order':
  352. $cost = $this->calc_percentage_adjustment( $cost, $cost_percent, $cost_operator, $package['contents_cost'] );
  353. break;
  354. }
  355. }
  356. return $cost;
  357. }
  358. }