PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 1.11.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v1.11.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
merchant / inc / modules / pre-orders / class-pre-orders-main-functionality.php

class-pre-orders-main-functionality.php in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 1.11.2, at inc/modules/pre-orders/class-pre-orders-main-functionality.php

1,431 lines 43.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Pre Orders.
5 *
6 * @package Merchant
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly
11 }
12
13 /**
14 * Pre Orders Class.
15 *
16 */
17 class Merchant_Pre_Orders_Main_Functionality {
18
19 /**
20 * Module ID.
21 *
22 */
23 const MODULE_ID = 'pre-orders';
24
25 /**
26 * @var string Date time format
27 */
28 public const DATE_TIME_FORMAT = 'm-d-Y h:i A';
29
30 /**
31 * Flag if the_title filter is added or not.
32 *
33 * @var bool
34 */
35 private $is_pre_order_filter_on = false;
36
37 /**
38 * Init.
39 *
40 * @return void
41 */
42 public function init() {
43 add_filter( 'woocommerce_add_to_cart_validation', array( $this, 'allow_one_type_only' ), 99, 2 );
44 add_filter( 'woocommerce_add_cart_item_data', array( $this, 'add_cart_item_data' ), 10, 4 );
45 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 );
47
48 // Cronjob.
49 if ( ! wp_next_scheduled( 'check_for_released_preorders' ) ) {
50 wp_schedule_event( time(), 'twicedaily', 'check_for_released_preorders' );
51 }
52
53 add_action( 'check_for_released_preorders', array( $this, 'check_for_pre_orders_and_maybe_update_status' ) );
54
55 add_filter( 'woocommerce_product_add_to_cart_text', array( $this, 'change_button_text' ), 10, 2 );
56 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 );
58 add_action( 'woocommerce_before_add_to_cart_form', array( $this, 'additional_information_before_cart_form' ) );
59 add_action( 'woocommerce_after_add_to_cart_form', array( $this, 'additional_information_after_cart_form' ) );
60
61 // Cart
62 add_filter( 'woocommerce_get_item_data', array( $this, 'cart_message_handler' ), 10, 2 );
63
64 add_action( 'woocommerce_order_item_meta_end', array( $this, 'order_item_meta_end' ), 10, 4 );
65 add_action( 'woocommerce_shop_loop_item_title', array( $this, 'shop_loop_item_title' ) );
66
67 // Products block.
68 add_filter( 'render_block_context', array( $this, 'add_block_title_filter' ), 10, 1 );
69 add_filter( 'woocommerce_blocks_product_grid_item_html', array( $this, 'override_product_grid_block' ), PHP_INT_MAX, 3 );
70
71 $this->register_pre_orders_order_status();
72
73 add_filter( 'wc_order_statuses', array( $this, 'add_pre_orders_order_statuses' ) );
74 add_filter( 'woocommerce_post_class', array( $this, 'pre_orders_post_class' ), 10, 3 );
75
76 add_filter( 'woocommerce_get_price_html', array( $this, 'dynamic_discount_price_html' ), 10, 2 );
77 add_action( 'woocommerce_before_calculate_totals', array( $this, 'dynamic_discount_cart_price' ) );
78 add_action( 'woocommerce_thankyou', array( $this, 'splitting_orders' ) );
79 add_filter( 'manage_woocommerce_page_wc-orders_columns', array( $this, 'shop_order_column' ), 11 );
80 add_filter( 'manage_edit-shop_order_columns', array( $this, 'shop_order_column' ), 11 );
81 add_action( 'manage_woocommerce_page_wc-orders_custom_column', array( $this, 'shop_order_column_content' ), 10, 2 );
82 add_action( 'manage_shop_order_posts_custom_column', array( $this, 'shop_order_column_content' ), 10, 2 );
83 }
84
85 /**
86 * Add shipping date column to the orders list.
87 *
88 * @param $columns array The columns.
89 *
90 * @return array The columns.
91 */
92 public function shop_order_column( $columns ) {
93 $columns['pre_order_shipping_date'] = esc_html__( 'Shipping Date', 'merchant' );
94
95 return $columns;
96 }
97
98 /**
99 * Display the shipping date in the orders list.
100 *
101 * @param $column string The column.
102 * @param $order WC_Order|int The order object in HPOS or order id.
103 *
104 * @return void
105 */
106 public function shop_order_column_content( $column, $order ) {
107 if ( $column === 'pre_order_shipping_date' ) {
108 if ( is_numeric( $order ) ) {
109 $order = wc_get_order( $order );
110 }
111 /**
112 * Filter the pre order shipping date.
113 *
114 * @param int $shipping_date The shipping date.
115 * @param WC_Order $order The order object.
116 *
117 * @since 1.9.9
118 */
119 $shipping_date = apply_filters(
120 'merchant_pre_order_shipping_date_column',
121 $order->get_meta( '_merchant_order_pre_order_shipping_date' ),
122 $order
123 );
124 if ( $shipping_date ) {
125 echo '<strong>' . esc_html( $this->convert_timestamp_to_human_readable( $shipping_date ) ) . '</strong>';
126 }
127 }
128 }
129
130 /**
131 * Convert timestamp to human readable date.
132 *
133 * @param $timestamp int The timestamp.
134 *
135 * @return string The human readable date.
136 */
137 private function convert_timestamp_to_human_readable( $timestamp ) {
138 $timezone = new DateTimeZone( merchant_timezone() );
139 $date = new \DateTime( 'now', $timezone );
140 $date->setTimestamp( $timestamp );
141
142 return $date->format( self::DATE_TIME_FORMAT );
143 }
144
145 /**
146 * Add offer details to the product.
147 *
148 * @param $cart_item_data array The cart item data.
149 * @param $product_id int The product ID.
150 * @param $variation_id int The variation ID.
151 * @param $quantity int The quantity.
152 *
153 * @return array The cart item data.
154 */
155 public function add_cart_item_data( $cart_item_data, $product_id, $variation_id, $quantity ) {
156 $product = wc_get_product( $product_id );
157 $offer = self::available_product_rule( $product_id );
158 if ( empty( $offer ) && $product->is_type( 'variable' ) ) {
159 $offer = self::available_product_rule( $variation_id );
160 }
161 if ( ! empty( $offer ) ) {
162 $cart_item_data['_merchant_pre_order'] = $offer;
163 $cart_item_data['_merchant_pre_order_shipping_date'] = $offer['shipping_timestamp'];
164 }
165
166 return $cart_item_data;
167 }
168
169 /**
170 * Add pre-order data to order item meta.
171 *
172 * @param $item_id int The item ID.
173 * @param $values array The values.
174 *
175 * @return void
176 */
177 public function add_order_item_meta( $item_id, $values ) {
178 if ( isset( $values['_merchant_pre_order'] ) ) {
179 wc_add_order_item_meta( $item_id, '_merchant_pre_order', $values['_merchant_pre_order'] );
180 }
181
182 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'] );
184 }
185 }
186
187 /**
188 * Splitting orders.
189 *
190 * @param $order_id int The order ID.
191 *
192 * @return void
193 */
194 public function splitting_orders( $order_id ) {
195 $order = wc_get_order( $order_id );
196 $mode = Merchant_Admin_Options::get( self::MODULE_ID, 'modes', 'unified_order' );
197
198 $this->mark_whole_order_as_pre_order( $order );
199
200 // if ( 'unified_order' === $mode || 'only_pre_orders' === $mode ) {
201 // $this->mark_whole_order_as_pre_order( $order );
202 // } elseif ( 'group_pre_order_into_one_order' === $mode ) {
203 // $this->group_pre_order_into_one_order( $order );
204 // } elseif ( 'separate_order_for_pre_orders' === $mode ) {
205 // $this->separate_order_for_pre_orders( $order );
206 // }
207 }
208
209 /**
210 * Mark whole order as pre-order if it contains pre-order products.
211 *
212 * @param $order WC_Order The order object.
213 *
214 * @return void
215 */
216 public function mark_whole_order_as_pre_order( $order ) {
217 $has_pre_order = 0;
218 $shipping_dates = array();
219 foreach ( $order->get_items() as $item_id => $item ) {
220 $product_id = $item->get_product_id();
221 if ( $this->is_pre_order( $product_id ) ) {
222 $shipping_dates[] = $item->get_meta( '_merchant_pre_order_shipping_date' );
223 ++ $has_pre_order;
224 }
225 }
226
227 if ( $has_pre_order ) {
228 $order->set_status( 'wc-pre-ordered' );
229 $order->add_meta_data( '_is_pre_order', true );
230 $order->add_meta_data( '_merchant_order_pre_order_shipping_date', max( $shipping_dates ) );
231 $order->save();
232 $this->trigger_emails( $order );
233 }
234 }
235
236 /**
237 * Group pre-order products into one order and keep the original order.
238 *
239 * @param WC_Order $order The order object.
240 *
241 * @return void
242 */
243 public function group_pre_order_into_one_order( $order ) {
244 // Get the original order
245 $original_order = $order;
246
247 // Store sub-orders IDs
248 $sub_order_ids = $pre_order_products = array();
249
250 // Loop through each product in the original order
251 foreach ( $original_order->get_items() as $item_id => $item ) {
252 if ( $this->is_pre_order( $item->get_product()->get_id() ) ) {
253 $pre_order_products[] = $item;
254 }
255 }
256
257 if ( ! empty( $pre_order_products ) ) {
258 $shipping_dates = array();
259 // Create a new order
260 $new_order = wc_create_order(
261 array(
262 'parent' => $original_order->get_id(),
263 )
264 );
265
266 // Copy order details from original order to new order.
267 $this->copy_order_details( $original_order, $new_order );
268
269 $new_order->add_meta_data( '_is_sub_order', true );
270 $new_order->add_meta_data( '_is_pre_order', true );
271 $new_order->set_status( 'wc-pre-ordered' );
272
273 // Copy order meta data from original order
274 foreach ( $original_order->get_meta_data() as $meta ) {
275 $new_order->add_meta_data( $meta->key, $meta->value );
276 }
277
278 // add pre-order products to the new order
279 foreach ( $pre_order_products as $item ) {
280 $rule = self::available_product_rule( $item->get_product()->get_id() );
281 $new_item = $this->clone_order_item( $item );
282 $new_item->add_meta_data( '_merchant_pre_order', $rule );
283 $new_item->add_meta_data( '_merchant_is_pre_order_product', true );
284 $new_item->add_meta_data( '_merchant_pre_order_shipping_date', $rule['shipping_timestamp'] );
285 $new_order->add_item( $new_item );
286 $shipping_dates[] = $rule['shipping_timestamp'];
287 }
288
289 // Calculate totals and set status
290 $new_order->calculate_totals();
291 $new_order->add_meta_data( '_merchant_order_pre_order_shipping_date', max( $shipping_dates ) );
292 $new_order->save();
293
294 $sub_order_ids[] = $new_order->get_id();
295
296 $this->trigger_emails( $new_order );
297
298 // Save sub-order IDs to the original order
299 $original_order->update_meta_data( '_sub_order_ids', $sub_order_ids );
300
301
302 // Update original order totals after removing items
303 $original_order->calculate_totals();
304 $original_order->save();
305 }
306 }
307
308 private function clone_order_item( $item ) {
309 $new_item = new WC_Order_Item_Product();
310 $new_item->set_product_id( $item->get_product_id() );
311 $new_item->set_variation_id( $item->get_variation_id() );
312 $new_item->set_name( $item->get_name() );
313 $new_item->set_quantity( $item->get_quantity() );
314 $new_item->set_subtotal( $item->get_subtotal() );
315 $new_item->set_total( $item->get_total() );
316 $new_item->set_taxes( $item->get_taxes() );
317 $new_item->set_meta_data( $item->get_meta_data() );
318
319 return $new_item;
320 }
321
322 /**
323 * Generate separate orders for each pre-order product.
324 *
325 * @param WC_Order $order The order object.
326 *
327 * @return void
328 */
329 public function separate_order_for_pre_orders( $order ) {
330 // Get the original order
331 $original_order = $order;
332
333 // Store sub-orders IDs
334 $sub_order_ids = array();
335
336 // Loop through each product in the original order
337 foreach ( $original_order->get_items() as $item_id => $item ) {
338 //check if the item is a product
339
340 // Get product details
341 $product_id = $item->get_product()->get_id();
342 if ( ! $product_id ) {
343 continue;
344 }
345
346 // check if the product is a pre-order
347 if ( ! $this->is_pre_order( $product_id ) ) {
348 continue;
349 }
350
351 // Create a new order
352 $new_order = wc_create_order(
353 array(
354 'parent' => $original_order->get_id(),
355 )
356 );
357
358 $new_item = $this->clone_order_item( $item );
359 $rule = self::available_product_rule( $product_id );
360 $new_item->add_meta_data( '_merchant_pre_order', $rule );
361 $new_item->add_meta_data( '_merchant_is_pre_order_product', true );
362 $new_item->add_meta_data( '_merchant_pre_order_shipping_date', $rule['shipping_timestamp'] );
363 $new_order->add_item( $new_item );
364
365 // Copy order details from original order to new order.
366 $this->copy_order_details( $original_order, $new_order );
367
368 // Calculate totals and set status
369 $new_order->calculate_totals();
370
371 // Save new order and store its ID
372 $new_order->add_meta_data( '_is_sub_order', true );
373 $new_order->add_meta_data( '_is_pre_order', true );
374 $new_order->add_meta_data( '_merchant_order_pre_order_shipping_date', $rule['shipping_timestamp'] );
375 $new_order->set_status( 'wc-pre-ordered' );
376 $new_order->save();
377 $this->trigger_emails( $new_order );
378 $sub_order_ids[] = $new_order->get_id();
379 }
380
381 // Save sub-order IDs to the original order
382 $original_order->update_meta_data( '_sub_order_ids', $sub_order_ids );
383
384 // Update original order totals after removing items
385 $original_order->calculate_totals();
386 $original_order->save();
387 }
388
389 /**
390 * Trigger sending emails for specific order.
391 *
392 * @param $order WC_Order The order object.
393 *
394 * @return void
395 */
396 private function trigger_emails( $order ) {
397 $mailer = WC()->mailer();
398 // Send customer email
399 // $customer_email_instance = $mailer->emails['WC_Email_Customer_Processing_Order'];
400 // $customer_email_instance->trigger( $order->get_id(), $order );
401
402 // Send admin email
403 $admin_email_instance = $mailer->emails['WC_Email_New_Order'];
404 $admin_email_instance->trigger( $order->get_id(), $order );
405 }
406
407 /**
408 * Hide order item meta that will be used for internal use only.
409 *
410 * @param $args array The hidden order item meta.
411 *
412 * @return array The hidden order item meta.
413 */
414 function hidden_order_itemmeta( $args ) {
415 $args[] = '_merchant_pre_order';
416 $args[] = '_merchant_is_pre_order_product';
417 $args[] = '_merchant_pre_order_shipping_date';
418
419 return $args;
420 }
421
422 /**
423 * Copy order details from the original order to the new child order.
424 *
425 * @param $original_order WC_Order The original order.
426 * @param $new_order WC_Order The new child order.
427 *
428 * @return void
429 */
430 private function copy_order_details( $original_order, $new_order ) {
431 $new_order->set_customer_id( $original_order->get_customer_id() );
432
433 // set customer billing details
434 $new_order->set_billing_first_name( $original_order->get_billing_first_name() );
435 $new_order->set_billing_last_name( $original_order->get_billing_last_name() );
436 $new_order->set_billing_phone( $original_order->get_billing_phone() );
437 $new_order->set_billing_email( $original_order->get_billing_email() );
438 $new_order->set_billing_address_1( $original_order->get_billing_address_1() );
439 $new_order->set_billing_address_2( $original_order->get_billing_address_2() );
440 $new_order->set_billing_city( $original_order->get_billing_city() );
441 $new_order->set_billing_state( $original_order->get_billing_state() );
442 $new_order->set_billing_postcode( $original_order->get_billing_postcode() );
443 $new_order->set_billing_country( $original_order->get_billing_country() );
444
445 // set customer shipping details
446 $new_order->set_shipping_first_name( $original_order->get_shipping_first_name() );
447 $new_order->set_shipping_last_name( $original_order->get_shipping_last_name() );
448 $new_order->set_shipping_address_1( $original_order->get_shipping_address_1() );
449 $new_order->set_shipping_address_2( $original_order->get_shipping_address_2() );
450 $new_order->set_shipping_city( $original_order->get_shipping_city() );
451 $new_order->set_shipping_state( $original_order->get_shipping_state() );
452 $new_order->set_shipping_postcode( $original_order->get_shipping_postcode() );
453 $new_order->set_shipping_country( $original_order->get_shipping_country() );
454
455 $new_order->set_customer_note( $original_order->get_customer_note() );
456 /**
457 * This action is documented in woocommerce/includes/class-wc-checkout.php
458 *
459 * @since 3.0.0 or earlier
460 */
461 $new_order->set_customer_id( apply_filters( 'woocommerce_checkout_customer_id', get_current_user_id() ) );
462 $new_order->set_customer_ip_address( WC_Geolocation::get_ip_address() );
463 $new_order->set_customer_user_agent( wc_get_user_agent() );
464 $new_order->set_currency( get_woocommerce_currency() );
465 $new_order->set_created_via( 'checkout' );
466
467 // Copy order meta data from original order
468 foreach ( $original_order->get_meta_data() as $meta ) {
469 $new_order->add_meta_data( $meta->key, $meta->value );
470 }
471 }
472
473 /**
474 * Update the product price html based on the offer.
475 *
476 * @return string The price html.
477 */
478 public function dynamic_discount_price_html( $html_price, $product ) {
479 /**
480 * Disable the sale price change.
481 *
482 * @param bool $disable_sale_price The sale price change status.
483 * @param string $html_price The price.
484 * @param WC_Product $product The product object.
485 *
486 * @since 1.9.9
487 */
488 if ( apply_filters( 'merchant_pre_order_disable_sale_price_html', false, $html_price, $product ) ) {
489 return $html_price;
490 }
491
492 if ( ( ! is_admin() || wp_doing_ajax() ) && in_array( $product->get_type(), $this->allowed_product_types(), true ) ) {
493 if ( '' === $product->get_price() ) {
494 return $html_price;
495 }
496
497 $offer = self::available_product_rule( $product->get_id() );
498
499 if ( empty( $offer ) && $product->is_type( 'variation' ) ) {
500 $offer = self::available_product_rule( $product->get_parent_id() );
501 $is_excluded = merchant_is_product_excluded( $product->get_id(), $offer );
502 if ( $is_excluded ) {
503 $offer = array();
504 }
505 }
506
507 if ( $product->is_type( 'variable' ) ) {
508 $html_price = $this->variable_product_price_html( $product, $offer, $html_price );
509 } else {
510 $html_price = $this->simple_product_price_html( $product, $offer, $html_price );
511 }
512 }
513
514 return $html_price;
515 }
516
517 /**
518 * Variable product price html.
519 *
520 * @param WC_Product $product The product object.
521 *
522 * @return string The price html.
523 */
524 private function variable_product_price_html( $product, $offer, $html_price ) {
525 $sale = self::get_rule_sale( $offer );
526 if ( ! $sale ) {
527 return $html_price;
528 }
529
530 $prices = array();
531 $variations = $product->get_children();
532
533 $regular_prices = array();
534 $sale_prices = array();
535
536 foreach ( $variations as $variation_id ) {
537 $variation = wc_get_product( $variation_id );
538 $variation_offer = self::available_product_rule( $variation_id );
539 $regular_price = $variation->get_regular_price();
540
541 $regular_prices[] = $regular_price;
542
543 if ( empty( $variation_offer ) ) {
544 $variation_offer = self::available_product_rule( $product->get_id() );
545
546 $is_excluded = merchant_is_product_excluded( $variation_id, $variation_offer );
547 if ( $is_excluded ) {
548 $prices[] = $regular_price;
549 $sale_prices[] = $regular_price;
550 continue;
551 }
552 }
553
554 if ( empty( $variation_offer ) ) {
555 if ( $variation->is_on_sale() ) {
556 $sale_price = wc_get_price_to_display( $variation );
557 } else {
558 $sale_price = $regular_price;
559 }
560 $prices[] = $sale_price;
561 $sale_prices[] = $sale_price;
562 continue;
563 }
564
565 $sale_price = $this->calculate_discounted_price( $regular_price, $variation_offer, $variation );
566 if ( $sale_price <= 0 ) {
567 // If the price is less than 0, set it to the regular/sale price
568 if ( $variation->is_on_sale() ) {
569 $sale_price = wc_get_price_to_display( $variation );
570 } else {
571 $sale_price = $regular_price;
572 }
573 }
574 $prices[] = $sale_price;
575 $sale_prices[] = $sale_price;
576 }
577
578 $regular_prices = array_unique( $regular_prices );
579 $sale_prices = array_unique( $sale_prices );
580
581 if ( 1 === count( $regular_prices ) && 1 === count( $sale_prices ) ) {
582 $regular_price = reset( $regular_prices );
583 $sale_price = reset( $sale_prices );
584 return wc_format_sale_price( $regular_price, $sale_price );
585 }
586
587 // Ensure $prices is not empty
588 if ( ! empty( $prices ) ) {
589 $min_price = min( $prices );
590 $max_price = max( $prices );
591
592 if ( $min_price !== $max_price ) {
593 return wc_format_price_range( $min_price, $max_price );
594 }
595
596 return wc_price( $min_price );
597 }
598
599 // If $prices is empty.
600 return '';
601 }
602
603 /**
604 * Simple product price html.
605 *
606 * @param WC_Product $product The product object.
607 * @param array $offer The offer details.
608 *
609 * @return string The price html.
610 */
611 private function simple_product_price_html( $product, $offer, $html_price ) {
612 $sale = self::get_rule_sale( $offer );
613 if ( ! $sale ) {
614 return $html_price;
615 }
616 $regular_price = wc_get_price_to_display( $product, array( 'price' => $product->get_regular_price() ) );
617 $discounted_price = $this->calculate_discounted_price( $regular_price, $offer, $product );
618
619 return wc_format_sale_price( $regular_price, $discounted_price );
620 }
621
622 /**
623 * Calculate the offer discount for certain price.
624 *
625 * @param float $price The price.
626 * @param array $offer The offer.
627 * @param WC_Product $product The product object (only supplied to the filter).
628 *
629 * @return float The discounted price.
630 */
631 public function calculate_discounted_price( $price, $offer, $product = null ) {
632 $sale = self::get_rule_sale( $offer );
633 $discount_type = $discount_value = '';
634 if ( $sale ) {
635 $discount_type = $offer['discount_type'];
636 $discount_value = $offer['discount_amount'];
637 if ( 'percentage' === $discount_type ) {
638 $price = (float) $price - ( (float) $price * ( (float) $discount_value / 100 ) );
639 } else {
640 $price = (float) $price - (float) $discount_value;
641 }
642 }
643
644 /**
645 * Filter the discounted price.
646 *
647 * @param float $price The price.
648 * @param array $offer The offer.
649 * @param string $discount_type The discount type.
650 * @param float $discount_value The discount value.
651 * @param WC_Product $product The product object.
652 *
653 * @since 1.9.5
654 */
655 return apply_filters(
656 'merchant_pre_order_sale_calculate_discounted_price',
657 $price,
658 $offer,
659 $discount_type,
660 $discount_value,
661 $product
662 );
663 }
664
665 /**
666 * Set the discounted cart price for the product.
667 *
668 * @param $cart_object array The cart object.
669 *
670 * @return void
671 */
672 public function dynamic_discount_cart_price() {
673 foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) {
674 /**
675 * Disable the cart price change.
676 *
677 * @param bool $disable_cart_price The cart price change status.
678 * @param array $cart_item The cart item.
679 * @param string $cart_item_key The cart item key.
680 *
681 * @since 1.9.7
682 */
683 if ( apply_filters( 'merchant_pre_order_sale_disable_cart_price', false, $cart_item, $cart_item_key ) ) {
684 continue;
685 }
686 $product_id = $cart_item['data']->get_id();
687 $product = wc_get_product( $product_id );
688
689 if ( ( ! is_admin() || wp_doing_ajax() ) && in_array( $product->get_type(), $this->allowed_product_types(), true ) ) {
690 $offer = self::available_product_rule( $product->get_id() );
691
692 if ( $product->is_type( 'variable' ) ) {
693 $product_id = $cart_item['variation_id'];
694 $product = wc_get_product( $product_id );
695 // check if the variation has an offer
696 $offer = self::available_product_rule( $product_id );
697 }
698
699 if ( empty( $offer ) && $product->is_type( 'variation' ) ) {
700 $offer = self::available_product_rule( $product->get_parent_id() );
701
702 $is_excluded = merchant_is_product_excluded( $product_id, $offer );
703 if ( $is_excluded ) {
704 continue;
705 }
706 }
707
708 if ( empty( $offer ) ) {
709 continue;
710 }
711 if ( ! isset( $offer['discount_toggle'] ) ) {
712 continue;
713 }
714 $regular_price = $product->get_regular_price();
715 $sale_price = $this->calculate_discounted_price( $regular_price, $offer, $product );
716 $cart_item['data']->set_price( $sale_price );
717 $cart_item['data']->set_meta_data( array( '_merchant_pre_order_sale' => $offer ) );
718 }
719 }
720 }
721
722 /**
723 * Validation (one type only).
724 *
725 * @param boolean $passed
726 * @param integer $product_id
727 *
728 * @return boolean
729 */
730 public function allow_one_type_only( $passed, $product_id ) {
731 $products = array_filter( WC()->cart->get_cart_contents() );
732 $pre_orders = 0;
733 $has_pre_orders = false;
734 foreach ( $products as $product ) {
735 $is_pre_order = $this->is_pre_order( $product['data']->get_id() );
736 if ( $is_pre_order ) {
737 ++ $pre_orders;
738 }
739 }
740 if ( $pre_orders ) {
741 $has_pre_orders = true;
742 }
743 $input_post_data = array(
744 'variation_id' => filter_input( INPUT_POST, 'variation_id', FILTER_SANITIZE_NUMBER_INT ),
745 );
746
747 $variable_id = ( isset( $input_post_data['variation_id'] ) ) ? sanitize_text_field( wp_unslash( $input_post_data['variation_id'] ) ) : 0;
748 $is_variable_has_pre_order = $this->is_pre_order( $product_id ) || $this->is_pre_order( $variable_id );
749
750 if ( empty( $products ) || ( $is_variable_has_pre_order && $has_pre_orders ) || ( false === $is_variable_has_pre_order && false === $has_pre_orders ) ) {
751 $passed = true;
752 } else {
753 $mode = Merchant_Admin_Options::get( self::MODULE_ID, 'modes', 'unified_order' );
754 if ( 'only_pre_orders' === $mode ) {
755 $passed = false;
756 if ( $is_variable_has_pre_order ) {
757 $notice = esc_html__( 'We detected that you are trying to add a pre-order product in your cart. Please remove the rest of the products before proceeding.',
758 'merchant' );
759 } else {
760 $notice = esc_html__( 'We detected that your cart has pre-order products. Please remove them before being able to add this product.', 'merchant' );
761 }
762 wc_add_notice( $notice, 'error' );
763 }
764 }
765
766 return $passed;
767 }
768
769 /**
770 * Check if product is a pre order item by product ID.
771 *
772 * @param integer $product_id
773 *
774 * @return boolean
775 */
776 public function is_pre_order( $product_id ) {
777 $available_pre_order = self::available_product_rule( $product_id );
778
779 return ! empty( $available_pre_order );
780 }
781
782 /**
783 * Check for pre orders and maybe update the status.
784 *
785 * @return void
786 */
787 public function check_for_pre_orders_and_maybe_update_status() {
788 $args = array(
789 'status' => 'wc-pre-ordered',
790 );
791
792 $pre_ordered_orders = wc_get_orders( $args );
793
794 foreach ( $pre_ordered_orders as $order ) {
795 $pre_order_date = $order->get_meta( '_merchant_order_pre_order_shipping_date' );
796
797 if ( $pre_order_date < time() ) {
798 $parent_order_id = $order->get_parent_id();
799
800 if ( 0 !== $parent_order_id ) {
801 $parent_order = wc_get_order( $parent_order_id );
802
803 if ( $parent_order->get_status() === 'completed' ) {
804 $order->update_status( 'processing', '[Merchant Pre Orders] ' );
805 }
806 } elseif ( $order->payment_complete() ) {
807 $order->update_status( 'processing', '[Merchant Pre Orders] ' );
808 }
809 }
810 }
811 }
812
813 /**
814 * Change pre-order button text.
815 *
816 * @param string $text
817 * @param WC_Product $product
818 *
819 * @return string
820 */
821 public function change_button_text( $text, $product ) {
822 if ( $product && $this->is_pre_order( $product->get_id() ) ) {
823 $pre_order_rule = self::available_product_rule( $product->get_id() );
824 $text = $pre_order_rule['button_text'] ? Merchant_Translator::translate( $pre_order_rule['button_text'] ) : esc_html__( 'Pre Order Now!', 'merchant' );
825 }
826
827 return $text;
828 }
829
830 /**
831 * Change pre-order button text for variable products.
832 *
833 * @param array $data
834 * @param object $product
835 * @param object $variation
836 *
837 * @return array
838 */
839 public function change_button_text_for_variable_products( $data, $product, $variation ) {
840 if ( $this->is_pre_order( $variation->get_id() ) ) {
841 $pre_order_rule = self::available_product_rule( $variation->get_id() );
842 $data['is_pre_order'] = true;
843
844 $additional_text = $pre_order_rule['additional_text'] ? Merchant_Translator::translate( $pre_order_rule['additional_text'] )
845 : esc_html__( 'Ships on {date}.', 'merchant' );
846 $time_format = date_i18n( get_option( 'date_format' ), $pre_order_rule['shipping_timestamp'] );
847 $text = $this->replace_date_text( $additional_text, $time_format );
848
849 if ( ! empty( $text ) ) {
850 $data['is_pre_order_date'] = $this->replace_date_text( $additional_text, $time_format );
851 }
852 }
853
854 return $data;
855 }
856
857 /**
858 * Replace {date} markup with new text.
859 *
860 * @param string $string_contains_date_tag
861 * @param string $time_format
862 *
863 * @return string
864 */
865 public function replace_date_text( $string_contains_date_tag, $time_format ) {
866 $from = array( '{date}' );
867 $to = array( $time_format );
868
869 return str_replace( $from, $to, $string_contains_date_tag );
870 }
871
872 /**
873 * Display pre order additional information before add to cart form.
874 *
875 * @return void
876 */
877 public function additional_information_before_cart_form() {
878 $input_post_data = array(
879 'product_id' => filter_input( INPUT_POST, 'product_id', FILTER_SANITIZE_NUMBER_INT ),
880 );
881
882 global $post, $product;
883
884 // Do not override globals.
885 $_post = $post;
886 $_product = $product;
887
888 // In some cases the $post might be null. e.g inside quick view popup.
889 if ( ! $_post && isset( $input_post_data['product_id'] ) ) {
890 $_post = get_post( absint( $input_post_data['product_id'] ) );
891 $_product = wc_get_product( $_post->ID );
892 }
893 if ( $this->is_pre_order( $_post->ID ) ) {
894 $pre_order_rule = self::available_product_rule( $_product->get_id() );
895 if ( ! empty( $pre_order_rule ) && $pre_order_rule['placement'] === 'before' ) {
896 $this->maybe_render_additional_information( $pre_order_rule );
897 }
898 }
899 }
900
901 /**
902 * Display pre order additional information after add to cart form.
903 *
904 * @return void
905 */
906 public function additional_information_after_cart_form() {
907 $input_post_data = array(
908 'product_id' => filter_input( INPUT_POST, 'product_id', FILTER_SANITIZE_NUMBER_INT ),
909 );
910
911 global $post, $product;
912
913 // Do not override globals.
914 $_post = $post;
915 $_product = $product;
916
917 // In some cases the $post might be null. e.g inside quick view popup.
918 if ( ! $_post && isset( $input_post_data['product_id'] ) ) {
919 $_post = get_post( absint( $input_post_data['product_id'] ) );
920 $_product = wc_get_product( $_post->ID );
921 }
922 if ( $_product && $this->is_pre_order( $_post->ID ) ) {
923 $pre_order_rule = self::available_product_rule( $_product->get_id() );
924 if ( ! empty( $pre_order_rule ) && $pre_order_rule['placement'] === 'after' ) {
925 $this->maybe_render_additional_information( $pre_order_rule );
926 }
927 }
928 }
929
930 /**
931 * Maybe render additional information.
932 *
933 * @return void
934 */
935 public function maybe_render_additional_information( $rule ) {
936 if ( ! empty( $rule ) ) {
937 $additional_text = $rule['additional_text'] ? Merchant_Translator::translate( $rule['additional_text'] )
938 : esc_html__( 'Ships on {date}.', 'merchant' );
939 $time_format = date_i18n( get_option( 'date_format' ), $rule['shipping_timestamp'] );
940 $text = $this->replace_date_text( $additional_text, $time_format );
941
942 printf( '<div class="merchant-pre-orders-date">%s</div>', esc_html( $text ) );
943 }
944 }
945
946 /**
947 * Register pre orders status.
948 *
949 * @return void
950 */
951 public function register_pre_orders_order_status() {
952 register_post_status( 'wc-pre-ordered', array(
953 'label' => esc_html__( 'Pre Ordered', 'merchant' ),
954 'public' => true,
955 'show_in_admin_status_list' => true,
956 'show_in_admin_all_list' => true,
957 'exclude_from_search' => false,
958 /* translators: %s: pre ordered product count */
959 'label_count' => _n_noop( 'Pre Ordered <span class="count">(%s)</span>', 'Pre Ordered <span class="count">(%s)</span>', 'merchant' ),
960 ) );
961 }
962
963 /**
964 * Add pre orders status to order statuses.
965 *
966 * @param array $order_statuses
967 *
968 * @return array
969 */
970 public function add_pre_orders_order_statuses( $order_statuses ) {
971 $order_statuses['wc-pre-ordered'] = esc_html__( 'Pre-Ordered', 'merchant' );
972
973 return $order_statuses;
974 }
975
976 /**
977 * Add pre orders class identifies to the body tag.
978 *
979 */
980 public function pre_orders_post_class( $classes, $product ) {
981 if ( $this->is_pre_order( $product->get_id() ) ) {
982 $classes[] = 'merchant-pre-ordered-product';
983 }
984
985 return $classes;
986 }
987
988 /**
989 * Filters cart item data to display cart.
990 *
991 * @param array $item_data Cart item data.
992 * @param array $cart_item Cart item array.
993 *
994 * @return array
995 *
996 * @see wc_get_formatted_cart_item_data for filter usage.
997 */
998 public function cart_message_handler( $item_data, $cart_item ) {
999 $product_id = $cart_item['product_id'];
1000 if ( $cart_item['data']->is_type( 'variation' ) ) {
1001 $product_id = $cart_item['variation_id'];
1002 }
1003 if ( $this->is_pre_order( $product_id ) ) {
1004 $pre_order_rule = self::available_product_rule( $product_id );
1005 if ( $pre_order_rule ) {
1006 $label_text = $pre_order_rule['cart_label_text'] ? Merchant_Translator::translate( $pre_order_rule['cart_label_text'] ) : esc_html__( 'Ships on', 'merchant' );
1007 $pre_order_date = date_i18n( get_option( 'date_format' ), $pre_order_rule['shipping_timestamp'] );
1008
1009 $item_data[] = array(
1010 'key' => $label_text,
1011 'value' => $pre_order_date,
1012 'display' => '',
1013 );
1014 }
1015 }
1016
1017 return $item_data;
1018 }
1019
1020 /**
1021 * filter name: woocommerce_order_item_meta_end
1022 * Adds pre order additional text to the order item name
1023 *
1024 * @param int $item_id Product ID.
1025 * @param \WC_Order_Item_Product $item Item array data.
1026 * @param \WC_Order $order Order data.
1027 * @param bool $plain_text Is plain text or not.
1028 *
1029 * @return string
1030 */
1031 public function order_item_meta_end( $item_id, $item, $order, $plain_text ) {
1032 echo $this->get_pre_order_text( $item->get_product()->get_id(), $plain_text ? '' : 'dl' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1033 }
1034
1035 /**
1036 * Render pre order text.
1037 */
1038 public function shop_loop_item_title() {
1039 echo $this->get_pre_order_text( get_the_ID(), 'span' ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
1040 }
1041
1042 /**
1043 * Add the_title filter for block rendering.
1044 *
1045 * @param array $context Default context.
1046 *
1047 * @return array
1048 */
1049 public function add_block_title_filter( $context ) {
1050 if ( ! empty( $context['postType'] ) && 'product' === $context['postType'] && ! $this->is_pre_order_filter_on ) {
1051 $this->is_pre_order_filter_on = true;
1052 add_filter( 'the_title', array( $this, 'block_add_the_title_filter' ), 10, 2 );
1053 add_filter( 'render_block', array( $this, 'block_remove_the_title_filter' ), 10, 3 );
1054 }
1055
1056 return $context;
1057 }
1058
1059 /**
1060 * Add pre order info to product grid block.
1061 *
1062 * @param string $html Product grid item HTML.
1063 * @param object $data Product data passed to the template.
1064 * @param \WC_Product $product Product object.
1065 *
1066 * @return string Updated product grid item HTML.
1067 */
1068 public function override_product_grid_block( $html, $data, $product ) {
1069 $pre_order_text = $this->get_pre_order_text( $product->get_id(), 'span' );
1070 if ( $pre_order_text ) {
1071 $html = str_replace( $data->title, $data->title . $pre_order_text, $html );
1072 }
1073
1074 return $html;
1075 }
1076
1077 /**
1078 * Adds pre order info to post_title.
1079 *
1080 * @param string $title Title.
1081 * @param int $post_id Post ID.
1082 *
1083 * @return string
1084 */
1085 public function block_add_the_title_filter( $title, $post_id ) {
1086 return $title . $this->get_pre_order_text( $post_id, 'span' );
1087 }
1088
1089 /**
1090 * Remove the_title filter when block rendering finished.
1091 *
1092 * @param string $block_content The block content.
1093 * @param array $parsed_block The full block, including name and attributes.
1094 * @param WP_Block $block The block instance.
1095 *
1096 * @return string
1097 */
1098 public function block_remove_the_title_filter( $block_content, $parsed_block, $block ) {
1099 if ( $this->is_pre_order_filter_on ) {
1100 remove_filter( 'the_title', array( $this, 'block_add_the_title_filter' ), 10 );
1101 $this->is_pre_order_filter_on = false;
1102 }
1103
1104 return $block_content;
1105 }
1106
1107 /**
1108 * Get pre order text by product ID.
1109 *
1110 * @param int $product_id
1111 * @param string $render_type Render template type string. 'span', 'dl', ''. Default ''.
1112 *
1113 * @return string
1114 */
1115 private function get_pre_order_text( $product_id, $render_type = '' ) {
1116 if ( ! $this->is_pre_order( $product_id ) ) {
1117 return '';
1118 }
1119
1120 $pre_order_rule = self::available_product_rule( $product_id );
1121 $label_text = $pre_order_rule['cart_label_text'] ? Merchant_Translator::translate( $pre_order_rule['cart_label_text'] ) : esc_html__( 'Ships on', 'merchant' );
1122 $pre_order_date = date_i18n( get_option( 'date_format' ), $pre_order_rule['shipping_timestamp'] );
1123 if ( 'span' === $render_type ) {
1124 return sprintf(
1125 '<span class="merchant-pre-orders-note"><span class="merchant-pre-orders-label">%s:</span><span>%s</span></span>',
1126 esc_html( $label_text ),
1127 $pre_order_date
1128 );
1129 } elseif ( 'dl' === $render_type ) {
1130 return sprintf(
1131 '<dl class="merchant-pre-orders-note"><dt>%s:</dt><dd>%s</dd></dl>',
1132 esc_html( $label_text ),
1133 $pre_order_date
1134 );
1135 } else {
1136 return sprintf(
1137 '%s: %s',
1138 esc_html( $label_text ),
1139 $pre_order_date
1140 );
1141 }
1142 }
1143
1144 /**
1145 * Get the product types that are allowed to have storewide sale.
1146 *
1147 * @return array The allowed product types.
1148 */
1149 public function allowed_product_types() {
1150 /**
1151 * Filter the product types that are allowed to have storewide sale.
1152 *
1153 * @param array $product_types
1154 *
1155 * @since 1.9.5
1156 */
1157 return apply_filters( 'merchant_pre_order_allowed_product_types', array(
1158 'simple',
1159 'variation',
1160 'variable',
1161 ) );
1162 }
1163
1164 /**
1165 * Get the pre-order rules.
1166 *
1167 * @param array $rule The rule to get.
1168 *
1169 * @return array|false The pre-order rules or false if there are no rule sale.
1170 */
1171 private static function get_rule_sale( $rule ) {
1172 $sale = false;
1173 if ( isset( $rule['discount_toggle'] ) && $rule['discount_toggle'] ) {
1174 $discount_type = $rule['discount_type'];
1175 $discount_amount = $rule['discount_amount'];
1176
1177 $sale = array(
1178 'discount_type' => $discount_type,
1179 'discount_amount' => $discount_amount,
1180 );
1181 }
1182
1183 /**
1184 * Filter the pre order sale.
1185 *
1186 * @param array $sale The pre order sale.
1187 * @param array $rule The pre order rule.
1188 *
1189 * @since 1.9.9
1190 */
1191 return apply_filters( 'merchant_pre_order_rule_sale', $sale, $rule );
1192 }
1193
1194 /**
1195 * Get the pre order rules.
1196 *
1197 * @return array The pre order rules.
1198 */
1199 private static function pre_order_rules() {
1200 return Merchant_Admin_Options::get( self::MODULE_ID, 'rules', array() );
1201 }
1202
1203 /**
1204 * Check if the rule is valid.
1205 *
1206 * @param array $rule The rule to check.
1207 *
1208 * @return boolean True if the rule is valid, false otherwise.
1209 */
1210 private static function is_valid_rule( $rule ) {
1211 if ( ! isset( $rule['trigger_on'] ) ) {
1212 return false;
1213 }
1214
1215 if ( 'product' === $rule['trigger_on'] && empty( $rule['product_ids'] ) ) {
1216 return false;
1217 }
1218
1219 if ( 'category' === $rule['trigger_on'] && empty( $rule['category_slugs'] ) ) {
1220 return false;
1221 }
1222
1223 if ( 'tags' === $rule['trigger_on'] && empty( $rule['tag_slugs'] ) ) {
1224 return false;
1225 }
1226
1227 if ( isset( $rule['discount_toggle'] ) && $rule['discount_toggle'] === true ) {
1228 if ( ! isset( $rule['discount_type'] ) ) {
1229 return false;
1230 }
1231 if ( ! isset( $rule['discount_amount'] ) ) {
1232 return false;
1233 }
1234 }
1235
1236 if ( isset( $rule['partial_payment_toggle'] ) && $rule['partial_payment_toggle'] === true ) {
1237 if ( ! isset( $rule['partial_payment_type'] ) ) {
1238 return false;
1239 }
1240 if ( ! isset( $rule['partial_payment_amount'] ) ) {
1241 return false;
1242 }
1243 }
1244
1245 $user_condition_passed = merchant_is_user_condition_passed( $rule );
1246 if ( ! $user_condition_passed ) {
1247 return false;
1248 }
1249
1250 if ( empty( $rule['shipping_date'] ) ) {
1251 return false;
1252 }
1253
1254 if ( empty( $rule['button_text'] ) ) {
1255 return false;
1256 }
1257
1258 if ( empty( $rule['placement'] ) ) {
1259 return false;
1260 }
1261
1262 return true;
1263 }
1264
1265 /**
1266 * Prepare the rule fields.
1267 *
1268 * @param array $rule The rule to prepare.
1269 *
1270 * @return array The prepared rule.
1271 */
1272 private static function prepare_rule( $rule ) {
1273 if ( 'product' === $rule['trigger_on'] ) {
1274 $rule['product_ids'] = array_map( 'intval', explode( ',', $rule['product_ids'] ) );
1275 }
1276
1277 if ( ! empty( $rule['pre_order_start'] ) ) {
1278 $rule['pre_order_start'] = merchant_convert_date_to_timestamp( $rule['pre_order_start'], self::DATE_TIME_FORMAT );
1279 }
1280
1281 if ( ! empty( $rule['pre_order_end'] ) ) {
1282 $rule['pre_order_end'] = merchant_convert_date_to_timestamp( $rule['pre_order_end'], self::DATE_TIME_FORMAT );
1283 }
1284
1285 $rule['shipping_timestamp'] = merchant_convert_date_to_timestamp( $rule['shipping_date'], self::DATE_TIME_FORMAT );
1286
1287 return $rule;
1288 }
1289
1290 /**
1291 * Get the available product rule.
1292 *
1293 * @param string $product_id The product ID.
1294 *
1295 * @return array The available product rule.
1296 */
1297 public static function available_product_rule( $product_id ) {
1298 $available_rule = array();
1299 $rules = self::pre_order_rules();
1300 $current_time = merchant_get_current_timestamp();
1301 $current_user = wp_get_current_user();
1302 foreach ( $rules as $rule ) {
1303 if ( self::is_valid_rule( $rule ) ) {
1304 $rule = self::prepare_rule( $rule );
1305
1306 // check if pre-order start date is set and if it is not in the future
1307 if ( ! empty( $rule['pre_order_start'] ) && $rule['pre_order_start'] > $current_time ) {
1308 continue;
1309 }
1310
1311 // check if pre-order end date is set and if it is in the past
1312 if ( ! empty( $rule['pre_order_end'] ) && $rule['pre_order_end'] < $current_time ) {
1313 continue;
1314 }
1315
1316 $user_condition_passed = merchant_is_user_condition_passed( $rule );
1317 if ( ! $user_condition_passed ) {
1318 continue;
1319 }
1320
1321 $trigger = $rule['trigger_on'] ?? 'product';
1322
1323 $is_excluded = merchant_is_product_excluded( $product_id, $rule );
1324 if ( $is_excluded ) {
1325 continue;
1326 }
1327
1328 if ( 'product' === $trigger && in_array( $product_id, $rule['product_ids'], true ) ) {
1329 $available_rule = $rule;
1330 break;
1331 } elseif ( 'category' === $trigger || 'tags' === $trigger ) {
1332 $taxonomy = $trigger === 'category' ? 'product_cat' : 'product_tag';
1333 $slugs = $trigger === 'category' ? ( $rule['category_slugs'] ?? array() ) : ( $rule['tag_slugs'] ?? array() );
1334
1335 $terms = get_the_terms( $product_id, $taxonomy );
1336 if ( ! empty( $terms ) ) {
1337 foreach ( $terms as $term ) {
1338 if ( in_array( $term->slug, $slugs, true ) ) {
1339 $available_rule = $rule;
1340 break;
1341 }
1342 }
1343 }
1344 } elseif ( 'all' === $trigger ) {
1345 $available_rule = $rule;
1346 }
1347 }
1348 }
1349
1350 /**
1351 * Filter the available product rule.
1352 *
1353 * @param array $available_rule The available product rule.
1354 * @param int $product_id The product ID.
1355 *
1356 * @return array The available product rule.
1357 *
1358 * @since 1.9.9
1359 */
1360 return apply_filters( 'merchant_pre_order_available_rule', $available_rule, $product_id );
1361 }
1362
1363 /**
1364 * Migrate old data to the new module storage.
1365 *
1366 * @return void
1367 */
1368 public static function data_migration() {
1369 $args = array(
1370 'post_type' => array( 'product', 'product_variation' ),
1371 'posts_per_page' => - 1,
1372 // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
1373 'meta_query' => array(
1374 'relation' => 'AND',
1375 array(
1376 'key' => '_pre_order_date',
1377 'compare' => 'EXISTS',
1378 ),
1379 array(
1380 'key' => '_pre_order_date',
1381 'compare' => '!=',
1382 'value' => '',
1383 ),
1384 array(
1385 'key' => '_is_pre_order',
1386 'compare' => 'EXISTS',
1387 ),
1388 array(
1389 'key' => '_is_pre_order',
1390 'compare' => 'in',
1391 'value' => array( 'yes', '1' ),
1392 ),
1393 ),
1394 );
1395 $migrated = get_option( 'merchant_pre_orders_migrated' );
1396 if ( ! $migrated ) {
1397 $products = new WP_Query( $args );
1398 if ( $products->have_posts() ) {
1399 $rules = self::pre_order_rules();
1400 while ( $products->have_posts() ) {
1401 $products->the_post();
1402 $product_id = get_the_ID();
1403 $pre_order_date = get_post_meta( $product_id, '_pre_order_date', true );
1404 $is_pre_order = get_post_meta( $product_id, '_is_pre_order', true );
1405 $pre_order_date = merchant_convert_date_to_timestamp( $pre_order_date, 'yy-m-d' );
1406 $pre_order_date = date_i18n( self::DATE_TIME_FORMAT, $pre_order_date );
1407 if ( $is_pre_order ) {
1408 $rules[] = array(
1409 'offer-title' => esc_html__( 'Custom Pre-order', 'merchant' ),
1410 'trigger_on' => 'product',
1411 'product_ids' => $product_id,
1412 'discount_toggle' => false,
1413 'user_condition' => 'all',
1414 'shipping_date' => $pre_order_date,
1415 'button_text' => esc_html__( 'Pre Order Now!', 'merchant' ),
1416 'additional_text' => esc_html__( 'Ships on {date}.', 'merchant' ),
1417 'placement' => 'before',
1418 'cart_label_text' => esc_html__( 'Ships on', 'merchant' ),
1419 'layout' => 'rule-details',
1420 );
1421 }
1422 }
1423 Merchant_Admin_Options::set( self::MODULE_ID, 'rules', $rules );
1424 }
1425 update_option( 'merchant_pre_orders_migrated', true );
1426 }
1427 }
1428 }
1429
1430 add_action( 'init', 'Merchant_Pre_Orders_Main_Functionality::data_migration' );
1431