PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
← All changes | inc/modules/pre-orders/class-pre-orders-main-functionality.php +348 -211 1.11.1 → 2.3.2 View file →
@@ -34,8 +34,13 @@
34 34 */
35 35 private $is_pre_order_filter_on = false;
36 36
37 37 /**
38 + * @var string The module ID.
39 + */
40 + private $module_id = 'pre-orders';
41 +
42 + /**
38 43 * Init.
39 44 *
40 45 * @return void
41 46 */
@@ -41,10 +46,12 @@
41 46 */
42 47 public function init() {
43 48 add_filter( 'woocommerce_add_to_cart_validation', array( $this, 'allow_one_type_only' ), 99, 2 );
44 49 add_filter( 'woocommerce_add_cart_item_data', array( $this, 'add_cart_item_data' ), 10, 4 );
50 + add_filter( 'woocommerce_add_cart_item_data', array( $this, 'record_add_to_cart_event' ), 11, 2 );
51 + add_action( 'woocommerce_after_calculate_totals', array( $this, 'update_analytics' ) );
45 52 add_filter( 'woocommerce_hidden_order_itemmeta', array( $this, 'hidden_order_itemmeta' ) );
46 - add_action( 'woocommerce_add_order_item_meta', array( $this, 'add_order_item_meta' ), 10, 2 );
53 + add_action( 'woocommerce_checkout_create_order_line_item', array( $this, 'add_order_item_meta' ), 10, 4 );
47 54
48 55 // Cronjob.
49 56 if ( ! wp_next_scheduled( 'check_for_released_preorders' ) ) {
50 57 wp_schedule_event( time(), 'twicedaily', 'check_for_released_preorders' );
@@ -53,18 +60,31 @@
53 60 add_action( 'check_for_released_preorders', array( $this, 'check_for_pre_orders_and_maybe_update_status' ) );
54 61
55 62 add_filter( 'woocommerce_product_add_to_cart_text', array( $this, 'change_button_text' ), 10, 2 );
56 63 add_filter( 'woocommerce_product_single_add_to_cart_text', array( $this, 'change_button_text' ), 10, 2 );
57 - add_filter( 'woocommerce_available_variation', array( $this, 'change_button_text_for_variable_products' ), 10, 3 );
64 + add_filter( 'woocommerce_available_variation', array( $this, 'attach_pre_order_data_to_variations' ), 10, 3 );
58 65 add_action( 'woocommerce_before_add_to_cart_form', array( $this, 'additional_information_before_cart_form' ) );
59 66 add_action( 'woocommerce_after_add_to_cart_form', array( $this, 'additional_information_after_cart_form' ) );
67 + add_action( 'woocommerce_before_add_to_cart_form', array( $this, 'record_pre_order_impression' ) );
60 68
61 69 // Cart
62 70 add_filter( 'woocommerce_get_item_data', array( $this, 'cart_message_handler' ), 10, 2 );
63 71
72 + /**
73 + * Hook 'merchant_is_pre_order_product' Checks if the product is a pre-order.
74 + *
75 + * @param int $product_id The product ID.
76 + *
77 + * @since 2.1.9
78 + */
79 + add_filter('merchant_is_pre_order_product', array( $this, 'is_pre_order' ), 10, 1 );
80 +
64 81 add_action( 'woocommerce_order_item_meta_end', array( $this, 'order_item_meta_end' ), 10, 4 );
65 82 add_action( 'woocommerce_shop_loop_item_title', array( $this, 'shop_loop_item_title' ) );
66 83
84 + add_action( 'woocommerce_checkout_create_order_line_item', array( $this, 'create_order_line_item' ), 10, 3 );
85 + add_action( 'woocommerce_checkout_order_created', array( $this, 'log_order_event' ) );
86 +
67 87 // Products block.
68 88 add_filter( 'render_block_context', array( $this, 'add_block_title_filter' ), 10, 1 );
69 89 add_filter( 'woocommerce_blocks_product_grid_item_html', array( $this, 'override_product_grid_block' ), PHP_INT_MAX, 3 );
70 90
@@ -135,15 +155,68 @@
135 155 * @return string The human readable date.
136 156 */
137 157 private function convert_timestamp_to_human_readable( $timestamp ) {
138 158 $timezone = new DateTimeZone( merchant_timezone() );
139 - $date = new \DateTime( 'now', $timezone );
140 - $date->setTimestamp( $timestamp );
159 + $date = new \DateTime( '@' . $timestamp ); // Create from UTC timestamp
160 + $date->setTimezone( $timezone ); // Convert to site timezone
141 161
142 162 return $date->format( self::DATE_TIME_FORMAT );
143 163 }
144 164
145 165 /**
166 + * Record add to cart event.
167 + *
168 + * @param $cart_item_data array Cart item data
169 + * @param $product_id int Product ID
170 + *
171 + * @return mixed
172 + */
173 + public function record_add_to_cart_event( $cart_item_data, $product_id ) {
174 + $user_id = WC()->session->get_customer_id();
175 + $analytics = new Merchant_Analytics_Logger();
176 + $analytics->set_user_id( $user_id );
177 + $module_id = $this->module_id;
178 + $impression_event = $analytics->get_product_module_last_impression( $module_id, $product_id );
179 + $existing_add_to_cart_event = $analytics->get_event_by(
180 + array(
181 + 'event_type' => 'add_to_cart',
182 + 'source_product_id' => $product_id,
183 + 'customer_id' => $user_id,
184 + 'module_id' => $this->module_id,
185 + )
186 + );
187 +
188 + $args = array(
189 + 'event_type' => 'add_to_cart',
190 + 'source_product_id' => $product_id,
191 + 'customer_id' => $user_id,
192 + 'campaign_id' => isset( $impression_event['campaign_id'] ) ? $impression_event['campaign_id'] : '',
193 + 'related_event_id' => isset( $impression_event['id'] ) ? $impression_event['id'] : '',
194 + 'module_id' => $this->module_id,
195 + 'meta_data' => isset( $impression_event['meta_data'] ) ? $impression_event['meta_data'] : '',
196 + );
197 +
198 + if ( null !== $existing_add_to_cart_event ) {
199 + // Check if this appeared in order event
200 + $appeared_in_order = $analytics->get_event_by( array(
201 + 'event_type' => 'order',
202 + 'source_product_id' => $product_id,
203 + 'customer_id' => $user_id,
204 + 'module_id' => $this->module_id,
205 + 'related_event_id' => $existing_add_to_cart_event['id'],
206 + ) );
207 +
208 + // Set the pre_order_add_to_cart_event_id based on whether the event exists
209 + $cart_item_data['pre_order_add_to_cart_event_id'] = null !== $appeared_in_order ? $analytics->log_event( $args ) : $existing_add_to_cart_event['id'];
210 + } else {
211 + // Log a new add to cart event if no previous event exists
212 + $cart_item_data['pre_order_add_to_cart_event_id'] = $analytics->log_event( $args );
213 + }
214 +
215 + return $cart_item_data;
216 + }
217 +
218 + /**
146 219 * Add offer details to the product.
147 220 *
148 221 * @param $cart_item_data array The cart item data.
149 222 * @param $product_id int The product ID.
@@ -166,22 +239,51 @@
166 239 return $cart_item_data;
167 240 }
168 241
169 242 /**
243 + * Update analytics.
244 + *
245 + * @param $cart WC_Cart Cart object
246 + *
247 + * @return void
248 + */
249 + public function update_analytics( $cart ) {
250 + $cart_items = $cart->get_cart();
251 + $user_id = WC()->session->get_customer_id();
252 + $analytics = new Merchant_Analytics_Logger();
253 + $analytics->set_user_id( $user_id );
254 + foreach ( $cart_items as $cart_item_key => $cart_item_data ) {
255 + if (
256 + isset( $cart_item_data['pre_order_add_to_cart_event_id'], $cart_item_data['_merchant_pre_order'] )
257 + && $analytics->event_exists( $cart_item_data['pre_order_add_to_cart_event_id'] )
258 + ) {
259 + $analytics->update_event( $cart_item_data['pre_order_add_to_cart_event_id'], array(
260 + 'campaign_id' => $cart_item_data['_merchant_pre_order']['flexible_id'],
261 + 'campaign_cost' => $cart_item_data['line_total'],
262 + 'meta_data' => wp_json_encode( $cart_item_data['_merchant_pre_order'] ),
263 + ) );
264 + }
265 + }
266 + }
267 +
268 + /**
170 269 * Add pre-order data to order item meta.
171 270 *
172 - * @param $item_id int The item ID.
271 + * @param $item WC_Order_Item The order item object.
272 + * @param $cart_item_key string The cart item key.
173 273 * @param $values array The values.
274 + * @param $order WC_Order The order object.
174 275 *
175 276 * @return void
277 + * @throws Exception
176 278 */
177 - public function add_order_item_meta( $item_id, $values ) {
279 + public function add_order_item_meta( $item, $cart_item_key, $values, $order ) {
178 280 if ( isset( $values['_merchant_pre_order'] ) ) {
179 - wc_add_order_item_meta( $item_id, '_merchant_pre_order', $values['_merchant_pre_order'] );
281 + $item->update_meta_data( '_merchant_pre_order', $values['_merchant_pre_order'] );
180 282 }
181 283
182 284 if ( isset( $values['_merchant_pre_order_shipping_date'] ) ) {
183 - wc_add_order_item_meta( $item_id, '_merchant_pre_order_shipping_date', $values['_merchant_pre_order_shipping_date'] );
285 + $item->update_meta_data( '_merchant_pre_order_shipping_date', $values['_merchant_pre_order_shipping_date'] );
184 286 }
185 287 }
186 288
187 289 /**
@@ -497,9 +599,9 @@
497 599 $offer = self::available_product_rule( $product->get_id() );
498 600
499 601 if ( empty( $offer ) && $product->is_type( 'variation' ) ) {
500 602 $offer = self::available_product_rule( $product->get_parent_id() );
501 - $is_excluded = merchant_is_product_excluded( $product->get_id(), $offer );
603 + $is_excluded = Merchant_Pre_Orders_Rules_Repository::is_product_excluded( $product->get_id(), $offer );
502 604 if ( $is_excluded ) {
503 605 $offer = array();
504 606 }
505 607 }
@@ -504,9 +606,9 @@
504 606 }
505 607 }
506 608
507 609 if ( $product->is_type( 'variable' ) ) {
508 - $html_price = $this->variable_product_price_html( $product );
610 + $html_price = $this->variable_product_price_html( $product, $offer, $html_price );
509 611 } else {
510 612 $html_price = $this->simple_product_price_html( $product, $offer, $html_price );
511 613 }
512 614 }
@@ -520,9 +622,14 @@
520 622 * @param WC_Product $product The product object.
521 623 *
522 624 * @return string The price html.
523 625 */
524 - private function variable_product_price_html( $product ) {
626 + private function variable_product_price_html( $product, $offer, $html_price ) {
627 + $sale = Merchant_Pre_Orders_Rules_Repository::get_rule_sale( $offer );
628 + if ( ! $sale ) {
629 + return $html_price;
630 + }
631 +
525 632 $prices = array();
526 633 $variations = $product->get_children();
527 634
528 635 $regular_prices = array();
@@ -537,9 +644,9 @@
537 644
538 645 if ( empty( $variation_offer ) ) {
539 646 $variation_offer = self::available_product_rule( $product->get_id() );
540 647
541 - $is_excluded = merchant_is_product_excluded( $variation_id, $variation_offer );
648 + $is_excluded = Merchant_Pre_Orders_Rules_Repository::is_product_excluded( $variation_id, $variation_offer );
542 649 if ( $is_excluded ) {
543 650 $prices[] = $regular_price;
544 651 $sale_prices[] = $regular_price;
545 652 continue;
@@ -603,9 +710,9 @@
603 710 *
604 711 * @return string The price html.
605 712 */
606 713 private function simple_product_price_html( $product, $offer, $html_price ) {
607 - $sale = self::get_rule_sale( $offer );
714 + $sale = Merchant_Pre_Orders_Rules_Repository::get_rule_sale( $offer );
608 715 if ( ! $sale ) {
609 716 return $html_price;
610 717 }
611 718 $regular_price = wc_get_price_to_display( $product, array( 'price' => $product->get_regular_price() ) );
@@ -623,9 +730,9 @@
623 730 *
624 731 * @return float The discounted price.
625 732 */
626 733 public function calculate_discounted_price( $price, $offer, $product = null ) {
627 - $sale = self::get_rule_sale( $offer );
734 + $sale = Merchant_Pre_Orders_Rules_Repository::get_rule_sale( $offer );
628 735 $discount_type = $discount_value = '';
629 736 if ( $sale ) {
630 737 $discount_type = $offer['discount_type'];
631 738 $discount_value = $offer['discount_amount'];
@@ -693,9 +800,9 @@
693 800
694 801 if ( empty( $offer ) && $product->is_type( 'variation' ) ) {
695 802 $offer = self::available_product_rule( $product->get_parent_id() );
696 803
697 - $is_excluded = merchant_is_product_excluded( $product_id, $offer );
804 + $is_excluded = Merchant_Pre_Orders_Rules_Repository::is_product_excluded( $product_id, $offer );
698 805 if ( $is_excluded ) {
699 806 continue;
700 807 }
701 808 }
@@ -707,8 +814,23 @@
707 814 continue;
708 815 }
709 816 $regular_price = $product->get_regular_price();
710 817 $sale_price = $this->calculate_discounted_price( $regular_price, $offer, $product );
818 + /**
819 + * Filter the discounted price.
820 + *
821 + * @param float $sale_price The discounted price.
822 + * @param array $cart_item The cart item.
823 + * @param string $cart_item_key The cart item key.
824 + *
825 + * @since 2.1.3
826 + */
827 + $sale_price = apply_filters(
828 + 'merchant_pre_order_cart_sale_price',
829 + $sale_price,
830 + $cart_item,
831 + $cart_item_key
832 + );
711 833 $cart_item['data']->set_price( $sale_price );
712 834 $cart_item['data']->set_meta_data( array( '_merchant_pre_order_sale' => $offer ) );
713 835 }
714 836 }
@@ -824,18 +946,22 @@
824 946
825 947 /**
826 948 * Change pre-order button text for variable products.
827 949 *
828 - * @param array $data
829 - * @param object $product
830 - * @param object $variation
950 + * @param array $data variation data.
951 + * @param WC_Product $product The product object.
952 + * @param WC_Product_Variation $variation The variation object.
831 953 *
832 954 * @return array
833 955 */
834 - public function change_button_text_for_variable_products( $data, $product, $variation ) {
835 - if ( $this->is_pre_order( $variation->get_id() ) ) {
836 - $pre_order_rule = self::available_product_rule( $variation->get_id() );
837 - $data['is_pre_order'] = true;
956 + public function attach_pre_order_data_to_variations( $data, $product, $variation ) {
957 + $pre_order_rule = self::available_product_rule( $variation->get_id() );
958 + if ( empty( $pre_order_rule ) ) {
959 + $pre_order_rule = self::available_product_rule( $product->get_id() );
960 + }
961 + if ( ! empty( $pre_order_rule ) ) {
962 + $data['is_pre_order'] = true;
963 + $data['pre_order_placement'] = isset( $pre_order_rule['placement'] ) ? $pre_order_rule['placement'] : 'before';
838 964
839 965 $additional_text = $pre_order_rule['additional_text'] ? Merchant_Translator::translate( $pre_order_rule['additional_text'] )
840 966 : esc_html__( 'Ships on {date}.', 'merchant' );
841 967 $time_format = date_i18n( get_option( 'date_format' ), $pre_order_rule['shipping_timestamp'] );
@@ -893,8 +1019,68 @@
893 1019 }
894 1020 }
895 1021
896 1022 /**
1023 + * Record pre-order impression on product page view.
1024 + *
1025 + * Hooked to `woocommerce_before_add_to_cart_form` independently of placement,
1026 + * so impressions are tracked regardless of where the pre-order message renders.
1027 + *
1028 + * @return void
1029 + */
1030 + public function record_pre_order_impression() {
1031 + $input_post_data = array(
1032 + 'product_id' => filter_input( INPUT_POST, 'product_id', FILTER_SANITIZE_NUMBER_INT ),
1033 + );
1034 +
1035 + global $post, $product;
1036 +
1037 + $_post = $post;
1038 + $_product = $product;
1039 +
1040 + if ( ! $_post && isset( $input_post_data['product_id'] ) ) {
1041 + $_post = get_post( absint( $input_post_data['product_id'] ) );
1042 + $_product = wc_get_product( $_post->ID );
1043 + }
1044 +
1045 + if ( ! $_post || ! $_product ) {
1046 + return;
1047 + }
1048 +
1049 + if ( $this->is_pre_order( $_post->ID ) ) {
1050 + $pre_order_rule = self::available_product_rule( $_product->get_id() );
1051 + if ( ! empty( $pre_order_rule ) ) {
1052 + $this->record_impression( $_product->get_id(), $pre_order_rule );
1053 + }
1054 + }
1055 + }
1056 +
1057 + /**
1058 + * Record impression.
1059 + *
1060 + * @return void
1061 + */
1062 + public function record_impression( $product_id, $campaign ) {
1063 + $analytics = new Merchant_Analytics_Logger();
1064 + $campaign_id = isset( $campaign['flexible_id'] ) ? $campaign['flexible_id'] : 0;
1065 + $module_id = $this->module_id;
1066 + $user_id = WC()->session->get_customer_id();
1067 + $analytics->set_user_id( $user_id );
1068 + $args = array(
1069 + 'source_product_id' => $product_id,
1070 + 'event_type' => 'impression',
1071 + 'related_event_id' => 0,
1072 + 'module_id' => $module_id,
1073 + 'campaign_id' => $campaign_id,
1074 + 'meta_data' => wp_json_encode( $campaign ),
1075 + );
1076 +
1077 + if ( $analytics->get_product_module_last_impression( $module_id, $product_id ) === null ) {
1078 + $analytics->log_event( $args );
1079 + }
1080 + }
1081 +
1082 + /**
897 1083 * Display pre order additional information after add to cart form.
898 1084 *
899 1085 * @return void
900 1086 */
@@ -992,10 +1178,18 @@
992 1178 */
993 1179 public function cart_message_handler( $item_data, $cart_item ) {
994 1180 $product_id = $cart_item['product_id'];
995 1181 if ( $cart_item['data']->is_type( 'variation' ) ) {
996 - $product_id = $cart_item['variation_id'];
1182 + $variation_id = $cart_item['variation_id'];
1183 + $parent_id = $cart_item['data']->get_parent_id();
1184 +
1185 + if ( ! $this->is_pre_order( $variation_id ) && $this->is_pre_order( $parent_id ) ) {
1186 + $product_id = $parent_id;
1187 + } else {
1188 + $product_id = $variation_id;
1189 + }
997 1190 }
1191 +
998 1192 if ( $this->is_pre_order( $product_id ) ) {
999 1193 $pre_order_rule = self::available_product_rule( $product_id );
1000 1194 if ( $pre_order_rule ) {
1001 1195 $label_text = $pre_order_rule['cart_label_text'] ? Merchant_Translator::translate( $pre_order_rule['cart_label_text'] ) : esc_html__( 'Ships on', 'merchant' );
@@ -1023,9 +1217,11 @@
1023 1217 *
1024 1218 * @return string
1025 1219 */
1026 1220 public function order_item_meta_end( $item_id, $item, $order, $plain_text ) {
1027 - echo $this->get_pre_order_text( $item->get_product()->get_id(), $plain_text ? '' : 'dl' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1221 + if( $item->get_product() ) {
1222 + echo $this->get_pre_order_text( $item->get_product()->get_id(), $plain_text ? '' : 'dl' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1223 + }
1028 1224 }
1029 1225
1030 1226 /**
1031 1227 * Render pre order text.
@@ -1034,8 +1230,72 @@
1034 1230 echo $this->get_pre_order_text( get_the_ID(), 'span' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1035 1231 }
1036 1232
1037 1233 /**
1234 + * Log order event.
1235 + * Only runs if bundle main product.
1236 + *
1237 + * @param $order WC_Order instance
1238 + *
1239 + * @return void
1240 + */
1241 + public function log_order_event( $order ) {
1242 + $module_id = $this->module_id;
1243 + $user_id = WC()->session->get_customer_id();
1244 + $analytics = new Merchant_Analytics_Logger();
1245 + $analytics->set_user_id( $user_id );
1246 + foreach ( $order->get_items() as $order_item ) {
1247 + $analytics_id = $order_item->get_meta( '_pre_order_add_to_cart_event_id' );
1248 + if ( ! empty( $analytics_id ) ) {
1249 + $add_to_cart_event_id = $analytics_id;
1250 + $event_data = $analytics->get_event( $add_to_cart_event_id );
1251 + $campaign_cost = ! empty( $event_data['campaign_cost'] ) ? $event_data['campaign_cost'] : 0;
1252 + $args = array(
1253 + 'source_product_id' => $order_item->get_product_id(),
1254 + 'event_type' => 'order',
1255 + 'related_event_id' => $add_to_cart_event_id,
1256 + 'campaign_cost' => $campaign_cost,
1257 + 'campaign_id' => ! empty( $event_data['campaign_id'] ) ? $event_data['campaign_id'] : 0,
1258 + 'module_id' => $module_id,
1259 + 'order_id' => $order->get_id(),
1260 + 'order_subtotal' => $order->get_subtotal(),
1261 + 'meta_data' => ! empty( $event_data['meta_data'] ) ? $event_data['meta_data'] : '',
1262 + );
1263 + if (
1264 + null === $analytics->get_event_by(
1265 + array(
1266 + 'source_product_id' => $order_item->get_product_id(),
1267 + 'event_type' => 'order',
1268 + 'related_event_id' => $add_to_cart_event_id,
1269 + 'module_id' => $module_id,
1270 + 'order_id' => $order->get_id(),
1271 + 'customer_id' => $user_id,
1272 + )
1273 + )
1274 + ) {
1275 + $analytics->log_event( $args );
1276 + }
1277 + }
1278 + }
1279 + }
1280 +
1281 + /**
1282 + * Add order item meta data.
1283 + *
1284 + * @param $order_item WC_Order_Item_Product instance
1285 + * @param $cart_item_key string Cart item key
1286 + * @param $values array Cart item values
1287 + *
1288 + * @return void
1289 + */
1290 + public function create_order_line_item( $order_item, $cart_item_key, $values ) {
1291 + if ( isset( $values['pre_order_add_to_cart_event_id'] ) ) {
1292 + // use _ to hide the data
1293 + $order_item->update_meta_data( '_pre_order_add_to_cart_event_id', $values['pre_order_add_to_cart_event_id'] );
1294 + }
1295 + }
1296 +
1297 + /**
1038 1298 * Add the_title filter for block rendering.
1039 1299 *
1040 1300 * @param array $context Default context.
1041 1301 *
@@ -1041,9 +1301,9 @@
1041 1301 *
1042 1302 * @return array
1043 1303 */
1044 1304 public function add_block_title_filter( $context ) {
1045 - if ( ! empty( $context['postType'] ) && 'product' === $context['postType'] && ! $this->is_pre_order_filter_on ) {
1305 + if ( ! empty( $context['postType'] ) && 'product' === $context['postType'] && ! $this->is_pre_order_filter_on && ! is_singular( 'product' ) ) {
1046 1306 $this->is_pre_order_filter_on = true;
1047 1307 add_filter( 'the_title', array( $this, 'block_add_the_title_filter' ), 10, 2 );
1048 1308 add_filter( 'render_block', array( $this, 'block_remove_the_title_filter' ), 10, 3 );
1049 1309 }
@@ -1108,9 +1368,18 @@
1108 1368 * @return string
1109 1369 */
1110 1370 private function get_pre_order_text( $product_id, $render_type = '' ) {
1111 1371 if ( ! $this->is_pre_order( $product_id ) ) {
1112 - return '';
1372 + $product = wc_get_product( $product_id );
1373 + if ( $product && $product->is_type( 'variation' ) ) {
1374 + $product_id = $product->get_parent_id();
1375 +
1376 + if ( ! $this->is_pre_order( $product_id ) ) {
1377 + return '';
1378 + }
1379 + } else {
1380 + return '';
1381 + }
1113 1382 }
1114 1383
1115 1384 $pre_order_rule = self::available_product_rule( $product_id );
1116 1385 $label_text = $pre_order_rule['cart_label_text'] ? Merchant_Translator::translate( $pre_order_rule['cart_label_text'] ) : esc_html__( 'Ships on', 'merchant' );
@@ -1156,223 +1425,90 @@
1156 1425 ) );
1157 1426 }
1158 1427
1159 1428 /**
1160 - * Get the pre order rules.
1429 + * Get the pre-order rules.
1161 1430 *
1162 1431 * @param array $rule The rule to get.
1163 1432 *
1164 - * @return array|false The pre order rules or false if there are no rule sale.
1433 + * @return array|false The pre-order rules or false if there are no rule sale.
1165 1434 */
1166 - private static function get_rule_sale( $rule ) {
1167 - $sale = false;
1168 - if ( isset( $rule['discount_toggle'] ) && $rule['discount_toggle'] ) {
1169 - $discount_type = $rule['discount_type'];
1170 - $discount_amount = $rule['discount_amount'];
1171 -
1172 - $sale = array(
1173 - 'discount_type' => $discount_type,
1174 - 'discount_amount' => $discount_amount,
1175 - );
1176 - }
1177 -
1178 - /**
1179 - * Filter the pre order sale.
1180 - *
1181 - * @param array $sale The pre order sale.
1182 - * @param array $rule The pre order rule.
1183 - *
1184 - * @since 1.9.9
1185 - */
1186 - return apply_filters( 'merchant_pre_order_rule_sale', $sale, $rule );
1187 - }
1188 -
1189 1435 /**
1190 - * Get the pre order rules.
1436 + * Get the available product rule.
1191 1437 *
1192 - * @return array The pre order rules.
1193 - */
1194 - private static function pre_order_rules() {
1195 - return Merchant_Admin_Options::get( self::MODULE_ID, 'rules', array() );
1196 - }
1197 -
1198 - /**
1199 - * Check if the rule is valid.
1438 + * @param int $product_id The product ID.
1200 1439 *
1201 - * @param array $rule The rule to check.
1202 - *
1203 - * @return boolean True if the rule is valid, false otherwise.
1440 + * @return array The available product rule.
1204 1441 */
1205 - private static function is_valid_rule( $rule ) {
1206 - if ( ! isset( $rule['trigger_on'] ) ) {
1207 - return false;
1208 - }
1442 + public static function available_product_rule( $product_id ) {
1443 + static $cache = array();
1209 1444
1210 - if ( 'product' === $rule['trigger_on'] && empty( $rule['product_ids'] ) ) {
1211 - return false;
1445 + if ( isset( $cache[ $product_id ] ) ) {
1446 + return $cache[ $product_id ];
1212 1447 }
1213 1448
1214 - if ( 'category' === $rule['trigger_on'] && empty( $rule['category_slugs'] ) ) {
1215 - return false;
1216 - }
1449 + $cache[ $product_id ] = Merchant_Pre_Orders_Rules_Repository::get_rule_for_product( $product_id );
1217 1450
1218 - if ( 'tags' === $rule['trigger_on'] && empty( $rule['tag_slugs'] ) ) {
1219 - return false;
1220 - }
1221 -
1222 - if ( isset( $rule['discount_toggle'] ) && $rule['discount_toggle'] === true ) {
1223 - if ( ! isset( $rule['discount_type'] ) ) {
1224 - return false;
1225 - }
1226 - if ( ! isset( $rule['discount_amount'] ) ) {
1227 - return false;
1228 - }
1229 - }
1230 -
1231 - if ( isset( $rule['partial_payment_toggle'] ) && $rule['partial_payment_toggle'] === true ) {
1232 - if ( ! isset( $rule['partial_payment_type'] ) ) {
1233 - return false;
1234 - }
1235 - if ( ! isset( $rule['partial_payment_amount'] ) ) {
1236 - return false;
1237 - }
1238 - }
1239 -
1240 - if ( ! isset( $rule['user_condition'] ) ) {
1241 - return false;
1242 - }
1243 -
1244 - if ( ( 'customers' === $rule['user_condition'] ) && empty( $rule['user_condition_users'] ) ) {
1245 - return false;
1246 - }
1247 -
1248 - if ( ( 'roles' === $rule['user_condition'] ) && empty( $rule['user_condition_roles'] ) ) {
1249 - return false;
1250 - }
1251 -
1252 - if ( empty( $rule['shipping_date'] ) ) {
1253 - return false;
1254 - }
1255 -
1256 - if ( empty( $rule['button_text'] ) ) {
1257 - return false;
1258 - }
1259 -
1260 - if ( empty( $rule['placement'] ) ) {
1261 - return false;
1262 - }
1263 -
1264 - return true;
1451 + return $cache[ $product_id ];
1265 1452 }
1266 1453
1267 1454 /**
1268 - * Prepare the rule fields.
1455 + * Migrate exclusion fields from old format to new individual toggles.
1269 1456 *
1270 - * @param array $rule The rule to prepare.
1271 - *
1272 - * @return array The prepared rule.
1457 + * @return void
1273 1458 */
1274 - private static function prepare_rule( $rule ) {
1275 - if ( 'product' === $rule['trigger_on'] ) {
1276 - $rule['product_ids'] = array_map( 'intval', explode( ',', $rule['product_ids'] ) );
1459 + public static function migrate_exclusion_fields() {
1460 + $option_name = 'merchant_' . self::MODULE_ID . '_ex_fields';
1461 + $migrated = get_option( $option_name, false );
1462 +
1463 + if ( $migrated ) {
1464 + return;
1277 1465 }
1278 1466
1279 - if ( ! empty( $rule['pre_order_start'] ) ) {
1280 - $rule['pre_order_start'] = merchant_convert_date_to_timestamp( $rule['pre_order_start'], self::DATE_TIME_FORMAT );
1281 - }
1467 + $rules = Merchant_Pre_Orders_Rules_Repository::get_global_rules();
1468 + if ( empty( $rules ) ) {
1469 + update_option( $option_name, true );
1282 1470
1283 - if ( ! empty( $rule['pre_order_end'] ) ) {
1284 - $rule['pre_order_end'] = merchant_convert_date_to_timestamp( $rule['pre_order_end'], self::DATE_TIME_FORMAT );
1471 + return;
1285 1472 }
1286 1473
1287 - $rule['shipping_timestamp'] = merchant_convert_date_to_timestamp( $rule['shipping_date'], self::DATE_TIME_FORMAT );
1474 + $needs_migration = false;
1288 1475
1289 - return $rule;
1290 - }
1476 + foreach ( $rules as $rule_key => $rule ) {
1477 + $n = 0;
1291 1478
1292 - /**
1293 - * Get the available product rule.
1294 - *
1295 - * @param string $product_id The product ID.
1296 - *
1297 - * @return array The available product rule.
1298 - */
1299 - public static function available_product_rule( $product_id ) {
1300 - $available_rule = array();
1301 - $rules = self::pre_order_rules();
1302 - $current_time = merchant_get_current_timestamp();
1303 - $current_user = wp_get_current_user();
1304 - foreach ( $rules as $rule ) {
1305 - if ( self::is_valid_rule( $rule ) ) {
1306 - $rule = self::prepare_rule( $rule );
1479 + // Check if individual toggles don't exist yet
1480 + if ( ! isset( $rule['exclude_products_toggle'] ) ) {
1481 + $rules[ $rule_key ]['exclude_products_toggle'] = ! empty( $rule['excluded_products'] );
1482 + ++ $n;
1483 + }
1307 1484
1308 - // check if pre-order start date is set and if it is not in the future
1309 - if ( ! empty( $rule['pre_order_start'] ) && $rule['pre_order_start'] > $current_time ) {
1310 - continue;
1311 - }
1485 + if ( ! isset( $rule['exclude_categories_toggle'] ) ) {
1486 + $rules[ $rule_key ]['exclude_categories_toggle'] = ! empty( $rule['excluded_categories'] );
1487 + ++ $n;
1488 + }
1312 1489
1313 - // check if pre-order end date is set and if it is in the past
1314 - if ( ! empty( $rule['pre_order_end'] ) && $rule['pre_order_end'] < $current_time ) {
1315 - continue;
1316 - }
1490 + if ( ! isset( $rule['exclude_tags_toggle'] ) ) {
1491 + $rules[ $rule_key ]['exclude_tags_toggle'] = ! empty( $rule['excluded_tags'] );
1492 + ++ $n;
1493 + }
1317 1494
1318 - if ( 'roles' === $rule['user_condition'] ) {
1319 - $allowed_roles = $rule['user_condition_roles'];
1320 - $user_roles = $current_user->roles;
1321 - $intersect = array_intersect( $allowed_roles, $user_roles );
1322 - if ( empty( $intersect ) ) {
1323 - continue;
1324 - }
1325 - }
1495 + if ( ! isset( $rule['exclude_brands_toggle'] ) ) {
1496 + $rules[ $rule_key ]['exclude_brands_toggle'] = ! empty( $rule['excluded_brands'] );
1497 + ++ $n;
1498 + }
1326 1499
1327 - if ( 'customers' === $rule['user_condition'] ) {
1328 - $allowed_users = $rule['user_condition_users'];
1329 - $current_user_id = $current_user->ID;
1330 - if ( ! in_array( $current_user_id, $allowed_users, true ) ) {
1331 - continue;
1332 - }
1333 - }
1500 + if ( $n > 0 ) {
1501 + $needs_migration = true; // Set to true if any iteration needs migration
1502 + }
1503 + }
1334 1504
1335 - $trigger = $rule['trigger_on'] ?? 'product';
1336 -
1337 - $is_excluded = merchant_is_product_excluded( $product_id, $rule );
1338 - if ( $is_excluded ) {
1339 - continue;
1340 - }
1341 -
1342 - if ( 'product' === $trigger && in_array( $product_id, $rule['product_ids'], true ) ) {
1343 - $available_rule = $rule;
1344 - break;
1345 - } elseif ( 'category' === $trigger || 'tags' === $trigger ) {
1346 - $taxonomy = $trigger === 'category' ? 'product_cat' : 'product_tag';
1347 - $slugs = $trigger === 'category' ? ( $rule['category_slugs'] ?? array() ) : ( $rule['tag_slugs'] ?? array() );
1348 -
1349 - $terms = get_the_terms( $product_id, $taxonomy );
1350 - if ( ! empty( $terms ) ) {
1351 - foreach ( $terms as $term ) {
1352 - if ( in_array( $term->slug, $slugs, true ) ) {
1353 - $available_rule = $rule;
1354 - break;
1355 - }
1356 - }
1357 - }
1358 - } elseif ( 'all' === $trigger ) {
1359 - $available_rule = $rule;
1360 - }
1361 - }
1505 + if ( $needs_migration ) {
1506 + Merchant_Admin_Options::set( self::MODULE_ID, 'rules', $rules );
1362 1507 }
1363 1508
1364 - /**
1365 - * Filter the available product rule.
1366 - *
1367 - * @param array $available_rule The available product rule.
1368 - * @param int $product_id The product ID.
1369 - *
1370 - * @return array The available product rule.
1371 - *
1372 - * @since 1.9.9
1373 - */
1374 - return apply_filters( 'merchant_pre_order_available_rule', $available_rule, $product_id );
1509 + // Mark migration as completed
1510 + update_option( $option_name, true );
1375 1511 }
1376 1512
1377 1513 /**
1378 1514 * Migrate old data to the new module storage.
@@ -1409,9 +1545,9 @@
1409 1545 $migrated = get_option( 'merchant_pre_orders_migrated' );
1410 1546 if ( ! $migrated ) {
1411 1547 $products = new WP_Query( $args );
1412 1548 if ( $products->have_posts() ) {
1413 - $rules = self::pre_order_rules();
1549 + $rules = Merchant_Pre_Orders_Rules_Repository::get_global_rules();
1414 1550 while ( $products->have_posts() ) {
1415 1551 $products->the_post();
1416 1552 $product_id = get_the_ID();
1417 1553 $pre_order_date = get_post_meta( $product_id, '_pre_order_date', true );
@@ -1441,4 +1577,5 @@
1441 1577 }
1442 1578 }
1443 1579
1444 1580 add_action( 'init', 'Merchant_Pre_Orders_Main_Functionality::data_migration' );
1581 +add_action( 'init', 'Merchant_Pre_Orders_Main_Functionality::migrate_exclusion_fields' );