PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.3.9.1
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.3.9.1
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
learnpress / inc / order / class-lp-order.php

class-lp-order.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.3.9.1, at inc/order/class-lp-order.php

1,528 lines 40.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Class LP_Order
4 *
5 * @author ThimPress
6 * @package LearnPress/Classes
7 * @version 4.0.1
8 */
9
10 use LearnPress\Databases\Order\LPOrderItemsDB;
11 use LearnPress\Databases\PostDB;
12 use LearnPress\Filters\Order\OrderItemsFilter;
13 use LearnPress\Filters\PostFilter;
14 use LearnPress\Models\UserItems\UserCourseModel;
15 use LearnPress\Models\UserItems\UserItemModel;
16 use LearnPress\Models\UserModel;
17
18 defined( 'ABSPATH' ) || exit();
19
20 if ( ! class_exists( 'LP_Order' ) ) {
21
22 /**
23 * Class LP_Order
24 */
25 class LP_Order extends LP_Abstract_Post_Data {
26
27 /**
28 * @var string
29 */
30 protected $_post_type = LP_ORDER_CPT;
31
32 /**
33 * @var array
34 */
35 protected $_data = array(
36 'user_id' => '',
37 'order_date' => '',
38 'date_modified' => '',
39 'customer_message' => '',
40 'customer_note' => '',
41 'status' => '',
42 'order_key' => '',
43 'total' => 0,
44 'subtotal' => 0,
45 'created_via' => '',
46 'checkout_email' => '',
47 );
48
49 /**
50 * @var array
51 */
52 protected $_meta_keys = array(
53 '_user_id' => 'user',
54 '_order_currency' => 'currency',
55 '_order_subtotal' => 'subtotal',
56 '_order_total' => 'total',
57 '_payment_method' => 'payment_method',
58 '_payment_method_title' => 'payment_method_title',
59 '_order_version' => 'order_version',
60 '_edit_last' => '',
61 '_edit_lock' => '',
62 '_prices_include_tax' => '',
63 '_order_key' => '',
64 '_user_ip' => '',
65 '_checkout_email' => '',
66 );
67
68 /**
69 * Store order status in transactions.
70 *
71 * @var array
72 */
73 protected $_status = array();
74
75 /**
76 * LP_Order constructor.
77 *
78 * @param bool $order_id
79 *
80 * @throws Exception
81 */
82 public function __construct( $order_id = false ) {
83 $this->_curd = new LP_Order_CURD();
84 if ( is_numeric( $order_id ) && $order_id > 0 ) {
85 $this->set_id( $order_id );
86 } elseif ( $order_id instanceof self ) {
87 $this->set_id( absint( $order_id->get_id() ) );
88 } elseif ( ! empty( $order_id->ID ) ) {
89 $this->set_id( absint( $order_id->ID ) );
90 }
91
92 if ( $this->get_id() > 0 ) {
93 $this->load();
94 }
95 }
96
97 /**
98 * Load the order data.
99 * Check if the id is not zero but it's post type does not exists.
100 *
101 * @throws Exception
102 */
103 public function load() {
104 $this->_curd->load( $this );
105 }
106
107 /**
108 * Set order date.
109 *
110 * @param int|string $date
111 */
112 public function set_order_date( $date ): LP_Order {
113 $this->set_data_date( 'order_date', $date );
114
115 return $this;
116 }
117
118 /**
119 * Get date of this order.
120 *
121 * @param string $context
122 *
123 * @return string|LP_Datetime
124 */
125 public function get_order_date( $context = '' ) {
126 $date = $this->get_data( 'order_date' );
127
128 if ( 'edit' !== $context ) {
129
130 if ( $date instanceof LP_Datetime ) {
131 $strtime = strtotime( $date->toSql() );
132
133 switch ( $context ) {
134 case 'd':
135 $date = date_i18n( 'Y-m-d', $strtime );
136 break;
137 case 'h':
138 $date = date_i18n( 'H', $strtime );
139 break;
140 case 'm':
141 $date = date_i18n( 'i', $strtime );
142 break;
143 case 'timestamp':
144 $date = $strtime;
145 break;
146 default:
147 $post = get_post( $this->get_id() );
148 $m_time = $post->post_date;
149 $time = get_post_time( 'G', true, $post );
150 $time_diff = time() - $time;
151
152 if ( $time_diff > 0 && $time_diff < DAY_IN_SECONDS ) {
153 $date = sprintf( __( '%s ago', 'learnpress' ), human_time_diff( $time ) );
154 } else {
155 $date = mysql2date( get_option( 'date_format' ), $m_time );
156 }
157 }
158 }
159 } elseif ( ! $date instanceof LP_Datetime ) {
160 $date = new LP_Datetime( $date );
161 }
162
163 return $date;
164 }
165
166 /**
167 * Set order key
168 *
169 * @param string $order_key
170 */
171 public function set_order_key( $order_key ) {
172 $this->_set_data( 'order_key', $order_key );
173 }
174
175 /**
176 * Get order key.
177 *
178 * @return array|mixed
179 */
180 public function get_order_key() {
181 return $this->get_data( 'order_key', '' );
182 }
183
184 /**
185 * Get confirm received text
186 *
187 * @return string
188 * @since 3.0.0
189 */
190 public function get_confirm_order_received_text() {
191 return apply_filters( 'learn-press/confirm-order-received-text', __( 'Thank you. Your order has been received.', 'learnpress' ), $this->get_id() );
192 }
193
194 /**
195 * Get thank you message after the order is placed.
196 *
197 * @return mixed
198 * @since 3.0.0
199 */
200 public function get_thankyou_message() {
201 return apply_filters( 'learn-press/confirm-order-received-text', __( 'Thank you. Your order has been received.', 'learnpress' ), $this->get_id() );
202 }
203
204 /**
205 * Checks to see if current order has status as passed.
206 *
207 * @param string|array $status String or an array of statuses
208 *
209 * @return mixed
210 */
211 public function has_status( $status ) {
212 settype( $status, 'array' );
213 $has = in_array( $this->get_status(), $status );
214
215 return apply_filters( 'learn-press/has-order-status', $has, $status, $this->get_id() );
216 }
217
218 /**
219 * Updates order to new status if needed
220 *
221 * @param mixed $new_status
222 * @param bool $manual Is this a manual order status change?.
223 *
224 * @return bool
225 */
226 public function update_status( $new_status = 'pending', $manual = false ): bool {
227 $result = false;
228
229 try {
230 $this->set_status( $new_status );
231 $result = $this->save();
232 } catch ( Throwable $e ) {
233 error_log( $e->getMessage() );
234 }
235
236 return $result;
237 }
238
239 /**
240 * Format order number id
241 *
242 * @return string
243 */
244 public function get_order_number(): string {
245 return learn_press_transaction_order_number( $this->get_id() );
246 }
247
248 /**
249 * Get status of the order
250 *
251 * @return mixed
252 */
253 public function get_order_status() {
254 return $this->get_status();
255 }
256
257 public function get_user_ip_address() {
258 return $this->get_data( 'user_ip_address' );
259 }
260
261 /**
262 * Get current status of order
263 *
264 * @return mixed
265 */
266 public function get_status() {
267 return $this->get_data( 'status', '' );
268 }
269
270 /**
271 * Get label of lp order status
272 *
273 * @param string $status
274 *
275 * @return string
276 * @since 4.2.0
277 * @version 1.0.0
278 */
279 public static function get_status_label( string $status ): string {
280 switch ( $status ) {
281 case LP_ORDER_COMPLETED:
282 $status = __( 'Completed', 'learnpress' );
283 break;
284 case LP_ORDER_PENDING:
285 $status = __( 'Pending', 'learnpress' );
286 break;
287 case LP_ORDER_PROCESSING:
288 $status = __( 'Processing', 'learnpress' );
289 break;
290 case LP_ORDER_CANCELLED:
291 $status = __( 'Cancelled', 'learnpress' );
292 break;
293 case LP_ORDER_FAILED:
294 $status = __( 'Failed', 'learnpress' );
295 break;
296 case LP_ORDER_TRASH:
297 $status = __( 'Trash', 'learnpress' );
298 break;
299 case 'on-hold':
300 $status = __( 'On hold', 'learnpress' );
301 break;
302 case 'refunded':
303 $status = __( 'Refunded', 'learnpress' );
304 break;
305 default:
306 $status = '';
307 break;
308 }
309
310 if ( ! is_string( $status ) ) {
311 $status = '';
312 }
313
314 return $status;
315 }
316
317 /**
318 * Get icons of lp order status
319 *
320 * @return array
321 * @since 4.2.0
322 * @version 1.0.0
323 */
324 public static function get_icons_status(): array {
325 $icons = [
326 LP_ORDER_COMPLETED => "<i class='dashicons dashicons-yes-alt'></i>",
327 LP_ORDER_PENDING => "<i class='dashicons dashicons-flag'></i>",
328 LP_ORDER_PROCESSING => "<i class='dashicons dashicons-clock'></i>",
329 LP_ORDER_CANCELLED => "<i class='dashicons dashicons-dismiss'></i>",
330 LP_ORDER_FAILED => "<i class='dashicons dashicons-warning'></i>",
331 ];
332
333 return apply_filters( 'lp/order/status/icons', $icons );
334 }
335
336 /**
337 * Set order status.
338 * $new_status shouldn't have prefix 'lp-'
339 *
340 * @param string $new_status
341 * @param string $note - Optional. Note for changing status.
342 *
343 * @sicne 3.0.0
344 * @version 1.0.1
345 */
346 public function set_status( string $new_status = '', string $note = '' ) {
347 // Ensure status not has prefix 'lp-'.
348 $new_status = str_replace( 'lp-', '', $new_status );
349 $valid_statuses = array_values( LP_Order::get_order_statuses() );
350 if ( ! in_array( $new_status, $valid_statuses ) && 'trash' !== $new_status ) {
351 $new_status = LP_ORDER_PENDING;
352 }
353
354 $this->_set_data( 'status', $new_status );
355 }
356
357 public function get_order_status_html() {
358 $order_status = self::get_status_label( $this->get_status() );
359 $status = ucfirst( $order_status );
360 $class = 'order-status order-status-' . sanitize_title( $status );
361 $html = sprintf( '<span class="%s">%s</span>', apply_filters( 'learn_press_order_status_class', $class, $status, $this ), $status );
362
363 return apply_filters( 'learn_press_order_status_html', $html, $this );
364 }
365
366 /**
367 * Mark order as complete
368 *
369 * @param string $transaction_id
370 *
371 * @return bool
372 */
373 public function payment_complete( $transaction_id = '' ): bool {
374 do_action( 'learn-press/payment-pre-complete', $this->get_id() );
375
376 $valid_order_statuses = apply_filters(
377 'learn-press/valid-order-statuses-for-payment-complete',
378 array(
379 'pending',
380 'processing',
381 ),
382 $this
383 );
384
385 if ( $this->get_id() && $this->has_status( $valid_order_statuses ) ) {
386
387 $this->update_status( 'completed' );
388
389 if ( ! empty( $transaction_id ) ) {
390 add_post_meta( $this->get_id(), '_transaction_id', $transaction_id, true );
391 }
392
393 do_action( 'learn-press/payment-complete', $this->get_id() );
394 } else {
395 do_action( 'learn-press/payment-complete-order-status-' . $this->get_status(), $this->get_id() );
396 }
397
398 return true;
399 }
400
401 /**
402 * Get checkout order successful url.
403 *
404 * @return string
405 */
406 public function get_checkout_order_received_url() {
407 $received_url = learn_press_get_endpoint_url( 'lp-order-received', $this->get_id(), learn_press_get_page_link( 'checkout' ) );
408
409 $received_url = add_query_arg( 'key', $this->get_order_key(), $received_url );
410
411 $received_url = apply_filters( 'learn_press_get_checkout_order_received_url', $received_url, $this );
412
413 /**
414 * @since 3.0.0
415 */
416 return apply_filters( 'learn-press/checkout-order-received-url', $received_url, $this );
417 }
418
419 /*********************************/
420
421 /**
422 * Get customer name of the order
423 */
424 public function get_customer_name() {
425 $customer_name = '';
426
427 if ( 'auto-draft' !== get_post_status( $this->get_id() ) ) {
428 $user_ids = $this->get_users();
429 if ( ! empty( $user_ids ) ) {
430 $customer_name = array();
431 foreach ( $user_ids as $uid ) {
432 $customer = learn_press_get_user( $uid );
433 if ( $customer && $customer->is_exists() ) {
434 if ( $customer->get_data( 'display_name' ) ) {
435 $customer_name[] = $customer->get_data( 'display_name' );
436 } elseif ( $customer->get_data( 'user_nicename' ) ) {
437 $customer_name[] = $customer->get_data( 'user_nicename' );
438 } elseif ( $customer->get_data( 'user_login' ) ) {
439 $customer_name[] = $customer->get_data( 'user_login' );
440 }
441 } else {
442 $customer_name[] = $this->get_guest_customer_name();
443 }
444 }
445
446 $customer_name = join( ', ', $customer_name );
447 }
448 }
449
450 if ( ! $customer_name ) {
451 $customer_name = $this->get_guest_customer_name();
452 }
453
454 return $customer_name;
455 }
456
457 public function get_guest_customer_name() {
458 $checkout_email = $this->get_checkout_email();
459 if ( $checkout_email ) {
460 $customer_name = sprintf( __( '%s (Guest)', 'learnpress' ), $checkout_email );
461 } else {
462 $customer_name = sprintf( __( '(Guest)', 'learnpress' ), $checkout_email );
463 }
464
465 return apply_filters( 'learn-press/order/guest-customer-name', $customer_name );
466 }
467
468 /*public function customer_exists() {
469 return false !== get_userdata( $this->get_data( 'user_id' ) );
470 }*/
471
472 /**
473 * Get items of the order only to show.
474 *
475 * @return mixed
476 */
477 public function get_items() {
478 $items = $this->_curd->read_items( $this );
479
480 return apply_filters( 'learn-press/order-items', $items );
481 }
482
483 /**
484 * Get all items of the order.
485 * Only use for add/remove items to learn (learn_press_user_items) table.
486 * Where call this function, must be careful, must set_time_limit( 0 );.
487 *
488 * @return array|null
489 * @version 1.0.1
490 * @since 4.2.7.2
491 */
492 public function get_all_items() {
493 global $wpdb;
494 $lp_order_items = LP_Database::getInstance();
495 $table_order_items = $lp_order_items->tb_lp_order_items;
496
497 $query = $wpdb->prepare(
498 "SELECT order_item_id, order_item_name, item_id, item_type
499 From $table_order_items
500 WHERE order_id = %d",
501 $this->get_id()
502 );
503
504 return $wpdb->get_results( $query, ARRAY_A );
505 }
506
507 /**
508 * Get items of the order by filter
509 *
510 * @param array $filter
511 *
512 * @return mixed
513 * @author tungnx
514 */
515 public function get_items_filter( $filter = array() ) {
516 $items = $this->_curd->read_items_filter( $this, $filter );
517
518 return apply_filters( 'learn-press/order-items', $items );
519 }
520
521 public function is_child() {
522 return $this->get_parent_id();
523 }
524
525 public function get_parent() {
526 return $this->get_parent_id() ? learn_press_get_order( $this->get_parent_id() ) : false;
527 }
528
529 /**
530 * Get list of course ids from order.
531 *
532 * @return array|bool
533 * @editor tungnx
534 */
535 public function get_item_ids() {
536 $items = $this->get_all_items();
537
538 if ( $items ) {
539 $course_ids = array();
540 foreach ( $items as $item ) {
541 if ( $item['item_type'] === LP_COURSE_CPT ) {
542 $course_ids[] = (int) $item['item_id'];
543 }
544 }
545
546 return $course_ids;
547 /**
548 * Comment by tungnx. Because it will error if item didn't have key 'course_id'.
549 * Ex: case buy certificate, will not have that key
550 */
551 //return @wp_list_pluck( $items, 'course_id' );
552 }
553
554 return false;
555 }
556
557 /**
558 * Check is order of Guest (user not login)
559 *
560 * @return bool
561 */
562 public function is_guest(): bool {
563 return ! get_user_by( 'ID', $this->get_user_id() );
564 }
565
566 public function get_item_meta( &$item ) {
567 $metas = get_metadata( 'learnpress_order_item', $item['id'] );
568 if ( $metas ) {
569 foreach ( $metas as $k => $v ) {
570 $item[ preg_replace( '!^_!', '', $k ) ] = LP_Helper::maybe_unserialize( $v[0] );
571 }
572 }
573 }
574
575 /**
576 * Remove all items from an order
577 */
578 public function remove_order_items() {
579 global $wpdb;
580 $wpdb->query(
581 $wpdb->prepare(
582 "
583 DELETE FROM itemmeta
584 USING $wpdb->learnpress_order_itemmeta itemmeta
585 INNER JOIN $wpdb->learnpress_order_items items
586 WHERE itemmeta.learnpress_order_item_id = items.order_item_id
587 AND items.order_id = %d",
588 $this->get_id()
589 )
590 );
591 $wpdb->query(
592 $wpdb->prepare(
593 "
594 DELETE FROM {$wpdb->learnpress_order_items}
595 WHERE order_id = %d",
596 $this->get_id()
597 )
598 );
599 $wpdb->query( $wpdb->prepare( "ALTER TABLE {$wpdb->learnpress_order_itemmeta} AUTO_INCREMENT = %d", 1 ) );
600 $wpdb->query( $wpdb->prepare( "ALTER TABLE {$wpdb->learnpress_order_items} AUTO_INCREMENT = %d", 1 ) );
601 }
602
603 /**
604 * Add a new item to order.
605 *
606 * @param array|int $item
607 *
608 * @return int
609 * @throws Exception
610 * @since 1.0.0
611 * @version 4.2.5
612 */
613 public function add_item( $item ): int {
614 global $wpdb;
615 $order_item_id = 0;
616
617 try {
618 if ( is_numeric( $item ) ) {
619 $item = array(
620 'item_id' => absint( $item ),
621 'order_item_name' => get_the_title( $item ),
622 );
623 }
624
625 $item_type = $item['item_type'] ?? get_post_type( $item['item_id'] );
626 if ( ! in_array( $item_type, learn_press_get_item_types_can_purchase() ) ) {
627 return false;
628 }
629
630 $item = wp_parse_args(
631 $item,
632 array(
633 'item_id' => 0,
634 'item_type' => '',
635 'order_item_name' => '',
636 'quantity' => 1,
637 'subtotal' => 0,
638 'total' => 0,
639 'meta' => array(),
640 )
641 );
642
643 switch ( $item_type ) {
644 case LP_COURSE_CPT:
645 $course = learn_press_get_course( $item['item_id'] );
646 $item['subtotal'] = apply_filters( 'learnpress/order/item/subtotal', $course->get_price() * $item['quantity'], $course, $item, $this );
647 $item['total'] = apply_filters( 'learnpress/order/item/total', $course->get_price() * $item['quantity'], $course, $item, $this );
648 $item['order_item_name'] = apply_filters( 'learnpress/order/item/title', get_post_field( 'post_title', $item['item_id'], 'raw' ), $course, $item, $this );
649 $item['meta']['_course_id'] = $item['item_id'];
650 break;
651 default:
652 $item = apply_filters( 'learnpress/order/add-item/item_type_' . $item_type, $item );
653 break;
654 }
655
656 // Insert new order item
657 $wpdb->insert(
658 $wpdb->learnpress_order_items,
659 array(
660 'order_item_name' => $item['order_item_name'],
661 'order_id' => $this->get_id(),
662 'item_id' => $item['item_id'],
663 'item_type' => $item_type,
664 ),
665 array(
666 '%s',
667 '%d',
668 '%d',
669 '%s',
670 )
671 );
672 $order_item_id = absint( $wpdb->insert_id );
673 // Clear cache
674 $key = "order/{$this->get_id()}/{$this->get_status()}/items";
675 LP_Cache::cache_load_first( 'clear', $key );
676 // End insert new order item
677
678 // Add learnpress_order_itemmeta
679 $item['meta']['_quantity'] = $item['quantity'] ?? 1;
680 $item['meta']['_subtotal'] = $item['subtotal'] ?? 0;
681 $item['meta']['_total'] = $item['total'] ?? 0;
682
683 if ( is_array( $item['meta'] ) ) {
684 foreach ( $item['meta'] as $k => $v ) {
685 learn_press_add_order_item_meta( $order_item_id, $k, $v );
686 }
687 }
688
689 do_action( 'learn-press/added-order-item-data', $order_item_id, $item, $this->get_id() );
690 } catch ( Throwable $e ) {
691 error_log( __FUNCTION__ . ': ' . $e->getMessage() );
692 }
693
694 return $order_item_id;
695 }
696
697 /**
698 * Set total
699 *
700 * @param int|float $total
701 */
702 public function set_total( $total = 0 ) {
703 $this->_set_data( 'total', $total );
704 }
705
706 /**
707 * Get total
708 *
709 * @return int|float
710 */
711 public function get_total() {
712 return $this->get_data( 'total', 0 );
713 }
714
715 /**
716 * @param float|int $subtotal
717 */
718 public function set_subtotal( $subtotal = 0 ) {
719 $this->_set_data( 'subtotal', $subtotal );
720 }
721
722 /**
723 * Get subtotal
724 *
725 * @return float
726 */
727 public function get_subtotal() {
728 return $this->get_data( 'subtotal' );
729 }
730
731 /**
732 * Remove an item from database and it's data.
733 *
734 * @param $order_item_id
735 *
736 * @return bool
737 * @throws Exception
738 */
739 public function remove_item( $order_item_id ): bool {
740 global $wpdb;
741
742 $item_id = absint( $order_item_id );
743 $order_item_id = absint( $order_item_id );
744
745 if ( ! $item_id ) {
746 return false;
747 }
748
749 do_action( 'learn_press_before_delete_order_item', $item_id );
750
751 /**
752 * @since 3.0.0
753 */
754 do_action( 'learn-press/before-delete-order-item', $item_id, $this->get_id() );
755
756 $filter = new OrderItemsFilter();
757 $filter->order_item_id = $order_item_id;
758 $filter->return_string_query = true;
759 $filter->run_query_count = false;
760 $order_items_db = LPOrderItemsDB::getInstance();
761 $order_items_db->get_query_single_row( $filter );
762 $query_single_row = $order_items_db->get_items( $filter );
763 $itemObj = $order_items_db->wpdb->get_row( $query_single_row );
764
765 if ( $itemObj && $itemObj->item_type === LP_COURSE_CPT ) {
766 $course_id = $itemObj->item_id;
767 $user_ids = $this->get_user_id();
768 if ( is_array( $user_ids ) ) {
769 foreach ( $user_ids as $user_id ) {
770 // Delete course item on learnpress_user_items if exists
771 $userCourseItem = UserItemModel::find_user_item(
772 $user_id,
773 $course_id,
774 LP_COURSE_CPT,
775 $this->get_id(),
776 LP_ORDER_CPT,
777 true
778 );
779 if ( $userCourseItem instanceof UserItemModel ) {
780 $userCourseModel = new UserCourseModel( $userCourseItem );
781 $userCourseModel->delete();
782 }
783 }
784 } else {
785 // Delete course item on learnpress_user_items if exists
786 $userCourseItem = UserItemModel::find_user_item(
787 $user_ids,
788 $course_id,
789 LP_COURSE_CPT,
790 $this->get_id(),
791 LP_ORDER_CPT,
792 true
793 );
794 if ( $userCourseItem instanceof UserItemModel ) {
795 $userCourseModel = new UserCourseModel( $userCourseItem );
796 $userCourseModel->delete();
797 }
798 }
799 }
800
801 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}learnpress_order_items WHERE order_item_id = %d", $item_id ) );
802 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}learnpress_order_itemmeta WHERE learnpress_order_item_id = %d", $item_id ) );
803
804 //Clear cache
805 $key = "order/{$this->get_id()}/{$this->get_status()}/items";
806 LP_Cache::cache_load_first( 'clear', $key );
807
808 /**
809 * @since 3.0.0
810 */
811 do_action( 'learn-press/deleted-order-item', $item_id, $this->get_id() );
812
813 do_action( 'learn_press_delete_order_item', $item_id );
814
815 return true;
816 }
817
818 /**
819 * Add multiple items.
820 *
821 * @param array $items
822 *
823 * @return array
824 */
825 public function add_items( $items ) {
826 settype( $items, 'array' );
827 $item_ids = array();
828 try {
829 foreach ( $items as $item ) {
830 $item_id = $this->add_item( $item );
831 if ( $item_id ) {
832 $item_ids[] = $item_id;
833 }
834 }
835 } catch ( Throwable $e ) {
836 error_log( $e->getMessage() );
837 }
838
839 return $item_ids;
840 }
841
842 /**
843 * @param string $field
844 *
845 * @return array|bool|int|LP_User|mixed
846 */
847 public function get_user( $field = '' ) {
848
849 $users = $this->get_users();
850 $uid = reset( $users );
851
852 $user = learn_press_get_user( $uid );
853 if ( false === $user ) {
854 return false;
855 }
856
857 if ( strtolower( $field ) == 'id' ) {
858 return $user->get_id();
859 }
860
861 if ( $field && $user ) {
862 return $user->get_data( $field );
863 }
864
865 return $user;
866 }
867
868 /**
869 * Get user id in array.
870 * user_id = 0 -> User type Guest
871 * @return int[]
872 * @editor tungnx
873 * @modify 4.1.4
874 * @version 1.0.1
875 */
876 public function get_users(): array {
877 $users = $this->get_user_id();
878 if ( $users !== - 1 ) {
879 settype( $users, 'array' );
880
881 $users = array_map(
882 function ( $user ) {
883 return absint( $user );
884 },
885 $users
886 );
887
888 $users = array_unique( $users );
889 } else {
890 $users = array();
891 }
892
893 return $users;
894 }
895
896 public function dropdown_users() {
897 $order_users = $this->get_users();
898 $users = get_users( array() );
899 echo '<select name="order-customer[]" id="order-customer" multiple="multiple">';
900 foreach ( (array) $users as $user ) {
901 // $user->get_id() = (int) $user->get_id();
902 if ( in_array( $user->get_id(), $order_users ) ) {
903 $found_selected = true;
904 } else {
905 $found_selected = false;
906 }
907 echo sprintf( '<option value="%d" %s>%s</option>', esc_attr( $user->get_id() ), selected( $found_selected, true, false ), esc_html( $user->user_login ) );
908 }
909 echo '</select>';
910 }
911
912
913 public function get_checkout_payment_url() {
914 }
915
916 public function get_formatted_order_subtotal() {
917 $currency_symbol = learn_press_get_currency_symbol( $this->get_currency() );
918
919 return learn_press_format_price( $this->get_subtotal(), $currency_symbol );
920 }
921
922 public function get_formatted_order_total() {
923 $currency_symbol = learn_press_get_currency_symbol( $this->get_currency() );
924
925 return learn_press_format_price( $this->get_total(), $currency_symbol );
926 }
927
928 public function get_currency() {
929 return $this->get_data( 'currency' ) ? $this->get_data( 'currency' ) : learn_press_get_currency();
930 }
931
932 public function set_currency( $value ) {
933 $this->_set_data( 'currency', $value );
934 }
935
936 /**
937 * Get payment method title.
938 *
939 * @return array|mixed
940 */
941 public function get_payment_method_title() {
942 return esc_html( $this->get_data( 'payment_method_title', '' ) );
943 }
944
945 /**
946 * Get view order detail url.
947 *
948 * @return string
949 */
950 public function get_view_order_url() {
951 global $wp_query;
952
953 $userModel = UserModel::find( get_current_user_id(), true );
954 $view_order_endpoint = LP_Settings::instance()->get( 'profile_endpoints.order-details' );
955
956 if ( ! $view_order_endpoint ) {
957 $view_order_endpoint = 'order-details';
958 }
959
960 if ( ! $userModel ) {
961 return '';
962 }
963
964 $user_slug = $userModel->user_nicename;
965 $link_profile = learn_press_get_page_link( 'profile' );
966
967 $view_order_endpoint = urlencode( $view_order_endpoint );
968 if ( get_option( 'permalink_structure' ) ) {
969 $view_order_url = $link_profile . $user_slug . '/' . $view_order_endpoint . '/' . $this->get_id() . '/';
970 } else {
971 $args = array(
972 'user' => $user_slug,
973 );
974 $args['view'] = $view_order_endpoint;
975
976 if ( $view_order_endpoint ) {
977 $args['id'] = $this->get_id();
978 }
979 $view_order_url = add_query_arg(
980 $args,
981 $link_profile
982 );
983 }
984
985 return apply_filters( 'learn_press_view_order_url', $view_order_url, $this );
986 }
987
988 /**
989 * Get cancel url if it's status is pending.
990 *
991 * @param bool $force
992 *
993 * @return mixed
994 */
995 public function get_cancel_order_url( $force = false ) {
996
997 $url = false;
998 if ( $this->has_status( 'pending' ) ) {
999 $user_id = get_current_user_id();
1000 $profile = LP_Profile::instance( $user_id );
1001 $url = $profile->get_tab_link( LP_Settings::instance()->get( 'profile_endpoints.orders' ) );
1002 if ( ! $force ) {
1003 $url = esc_url_raw( add_query_arg( 'cancel-order', $this->get_id(), $url ) );
1004 } else {
1005 $url = esc_url_raw( add_query_arg( 'cancelled-order', $this->get_id(), $url ) );
1006 }
1007
1008 $url = wp_nonce_url( $url, 'cancel-order', 'lp-nonce' );
1009 }
1010
1011 return apply_filters( 'learn-press/order-cancel-url', $url, $this->get_id() );
1012 }
1013
1014 /**
1015 * Get profile order's actions.
1016 *
1017 * @return array|mixed
1018 */
1019 public function get_profile_order_actions() {
1020 $actions = array(
1021 'view' => array(
1022 'url' => $this->get_view_order_url(),
1023 'text' => __( 'View', 'learnpress' ),
1024 ),
1025 );
1026
1027 $cancel_url = $this->get_cancel_order_url();
1028 if ( $cancel_url ) {
1029 $actions['cancel'] = array(
1030 'url' => $this->get_cancel_order_url(),
1031 'text' => __( 'Cancel', 'learnpress' ),
1032 );
1033 }
1034
1035 $payment_method = sanitize_key( (string) get_post_meta( $this->get_id(), '_payment_method', true ) );
1036 $subscription_status = get_post_meta( $this->get_id(), LP_Gateway_Abstract::META_SUBSCRIPTION_STATUS, true );
1037 if ( ! empty( $payment_method ) && in_array( $subscription_status, array( 'active', 'trialing', 'past_due' ), true ) ) {
1038 $gateway = LP_Gateways::instance()->get_gateway( $payment_method );
1039 if ( $gateway instanceof LP_Gateway_Abstract ) {
1040 $manage_url = $gateway->get_manage_subscription_url( $this );
1041 if ( ! empty( $manage_url ) ) {
1042 $actions['manage-subscription'] = array(
1043 'url' => esc_url_raw( $manage_url ),
1044 'text' => __( 'Manage subscription', 'learnpress' ),
1045 );
1046 }
1047 }
1048 }
1049
1050 $actions = apply_filters( 'learn-press/profile-order-actions', $actions, $this->get_id() );
1051
1052 return $actions;
1053 }
1054
1055 public function add_note( $note = null ) {
1056 $comment_author = '';
1057 $comment_author_email = '';
1058 if ( is_user_logged_in() ) {
1059 $user = get_user_by( 'id', get_current_user_id() );
1060 $comment_author = $user->display_name;
1061 $comment_author_email = $user->user_email;
1062 }
1063
1064 $comment_post_ID = $this->get_id();
1065 $comment_author_url = '';
1066 $comment_content = $note;
1067 $comment_agent = 'LearnPress';
1068 $comment_type = 'lp_order_note';
1069 $comment_parent = 0;
1070 $comment_approved = 1;
1071
1072 $commentdata = apply_filters(
1073 'learn_press_new_order_note_data',
1074 compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_agent', 'comment_type', 'comment_parent', 'comment_approved' ),
1075 $this->get_id()
1076 );
1077
1078 return wp_insert_comment( $commentdata );
1079 }
1080
1081 /**
1082 * Get user_name
1083 *
1084 * @param int $user_id
1085 *
1086 * @return string
1087 */
1088 public function get_user_name( int $user_id = 0 ): string {
1089 $user = learn_press_get_user( $user_id );
1090
1091 if ( $user && ! $user instanceof LP_User_Guest ) {
1092 $user_name = $user->get_display_name();
1093 } else {
1094 $user_name = $this->get_checkout_email();
1095 }
1096
1097 return apply_filters( 'learn-press/order/user-name', sprintf( _x( '%1$s', 'full name', 'learnpress' ), $user_name ) );
1098 }
1099
1100 /**
1101 * Check to see if this order is for multi users
1102 *
1103 * @return bool
1104 * @since 2.1.5
1105 */
1106 public function is_multi_users(): bool {
1107 return is_array( $this->get_user_id() );
1108 }
1109
1110 /**
1111 * Get email of user has bought this order.
1112 * In case this order is for multi users return an array with multi email addresses.
1113 *
1114 * @return array
1115 * @since 2.1.5
1116 */
1117 public function get_user_data(): array {
1118 $data = array();
1119
1120 $user_ids = $this->get_users();
1121 if ( ! empty( $user_ids ) ) {
1122 foreach ( $user_ids as $user_id ) {
1123 $user = learn_press_get_user( $user_id );
1124 if ( $user && $user->is_exists() ) {
1125 $data[ $user_id ] = $user->get_data(
1126 array(
1127 'id',
1128 'email',
1129 'user_login',
1130 'description',
1131 'first_name',
1132 'last_name',
1133 'nickname',
1134 'display_name',
1135 )
1136 );
1137 }
1138 }
1139 }
1140
1141 return $data;
1142 }
1143
1144 /**
1145 * Get email's user by user_id
1146 *
1147 * @param int $user_id
1148 *
1149 * @return string
1150 * @editor tungnx
1151 * @modify 4.1.3
1152 * @version 1.0.3
1153 */
1154 public function get_user_email( int $user_id = 0 ): string {
1155 if ( $user_id == 0 ) {
1156 $user_id = (int) $this->get_user_id();
1157 }
1158
1159 $userModel = UserModel::find( $user_id, true );
1160 if ( $userModel instanceof UserModel ) {
1161 $email = $userModel->get_email();
1162 } else {
1163 $email = $this->get_checkout_email();
1164 }
1165
1166 return $email;
1167 }
1168
1169 /**
1170 * Order title
1171 *
1172 * @param string $context
1173 *
1174 * @return array|mixed
1175 */
1176 public function get_title( $context = '' ) {
1177 return $this->get_data( 'order_title', __( 'Order on', 'learnpress' ) . ' ' . current_time( 'l jS F Y h:i:s A' ) );
1178 }
1179
1180 public function get_parent_id() {
1181 return $this->get_data( 'parent_id', 0 );
1182 }
1183
1184 public function set_parent_id( $parent_id ) {
1185 $this->_set_data( 'parent_id', $parent_id );
1186 }
1187
1188 public function set_user_id( $user_id ) {
1189 $this->_set_data( 'user_id', $user_id );
1190 }
1191
1192 /**
1193 * Set 1 for is user guest
1194 *
1195 * @param int $user_type_guest
1196 */
1197 public function set_user_type_guest( int $user_type_guest ) {
1198 $this->_set_data( 'user_type_guest', $user_type_guest );
1199 }
1200
1201 /**
1202 * Get user's ids of order.
1203 *
1204 * @return array|int
1205 */
1206 public function get_user_id() {
1207 return $this->get_data( 'user_id', - 1 );
1208 }
1209
1210 /**
1211 * Get date modified of order.
1212 *
1213 * @return LP_Datetime
1214 */
1215 public function get_date_modified() {
1216 return $this->get_data( 'date_modified' );
1217 }
1218
1219 /**
1220 * Set date modified of order.
1221 *
1222 * @param mixed $date
1223 */
1224 public function set_date_modified( $date ) {
1225 $this->_set_data( 'date_modified', $date );
1226 }
1227
1228 /**
1229 * Set method for creating the order, such as: checkout
1230 *
1231 * @param string $created_via
1232 */
1233 public function set_created_via( $created_via ) {
1234 $this->_set_data( 'created_via', $created_via );
1235 }
1236
1237 /**
1238 * Get method which order is created, such as: checkout
1239 *
1240 * @return string
1241 */
1242 public function get_created_via(): string {
1243 return $this->get_data( 'created_via', '' );
1244 }
1245
1246 /**
1247 * Save order data.
1248 *
1249 * @return mixed
1250 *
1251 * @throws Exception
1252 */
1253 public function save() {
1254 $old_status = '';
1255
1256 if ( $this->get_id() ) {
1257 $old_status_post = get_post_status( $this->get_id() );
1258 if ( ! in_array( $old_status_post, [ 'trash', 'auto-draft' ] ) ) {
1259 $old_status = str_replace( 'lp-', '', $old_status_post );
1260 } else {
1261 $old_status = $old_status_post;
1262 }
1263
1264 $return = $this->_curd->update( $this );
1265 } else {
1266 $return = $this->_curd->create( $this );
1267 $this->_id = $return;
1268 }
1269
1270 $new_status_post = get_post_status( $this->get_id() );
1271 if ( ! in_array( $new_status_post, [ 'trash', 'auto-draft' ] ) ) {
1272 $new_status = str_replace( 'lp-', '', $new_status_post );
1273 } else {
1274 $new_status = $new_status_post;
1275 }
1276
1277 $order_id = $this->get_id();
1278
1279 if ( $new_status !== $old_status ) {
1280 do_action( 'learn-press/order/status-changed', $order_id, $old_status, $new_status );
1281 do_action( 'learn-press/order/status-' . $new_status, $order_id, $old_status );
1282 do_action( 'learn-press/order/status-' . $old_status . '-to-' . $new_status, $order_id );
1283 }
1284
1285 //$this->_save_status();
1286
1287 return $return;
1288 }
1289
1290 /**
1291 * Get an instance of LP_Order by post ID or WP_Post object
1292 *
1293 * @param $order
1294 * @param $force
1295 *
1296 * @return LP_Order
1297 */
1298 public static function instance( $order, $force = true ) {
1299 // learn_press_deprecated_function( 'new LP_Order', '3.0', 'learn_press_get_order' );
1300
1301 return learn_press_get_order( $order );
1302 }
1303
1304 /** Getter/Setter **/
1305
1306 public function set_customer_message( $message ) {
1307 $this->_set_data( 'customer_message', $message );
1308 }
1309
1310 public function set_customer_note( $note ) {
1311 $this->_set_data( 'customer_note', $note );
1312 }
1313
1314 public function get_customer_note() {
1315 return $this->get_data( 'customer_note' ) . '';
1316 }
1317
1318 public function set_user_ip_address( $value ) {
1319 $this->_set_data( 'user_ip_address', $value );
1320 }
1321
1322 public function set_user_agent( $value ) {
1323 $this->_set_data( 'user_agent', $value );
1324 }
1325
1326 public function get_user_agent() {
1327 return $this->get_data( 'user_agent' );
1328 }
1329
1330 public function set_checkout_email( $email ) {
1331 $this->_set_data( 'checkout_email', $email );
1332 }
1333
1334 /**
1335 * Get email checkout for case Guest
1336 *
1337 * @return string
1338 */
1339 public function get_checkout_email(): string {
1340 return $this->get_data( 'checkout_email', '' );
1341 }
1342
1343 /**
1344 * Short function to check order is completed.
1345 *
1346 * @return bool
1347 */
1348 public function is_completed(): bool {
1349 return $this->get_status() === LP_ORDER_COMPLETED;
1350 }
1351
1352 /**
1353 * Check Order is created manual.
1354 *
1355 * @return bool
1356 * @since 4.1.3
1357 * @author tungnx
1358 * @version 1.0.0
1359 */
1360 public function is_manual(): bool {
1361 return $this->get_created_via() === LP_ORDER_CREATED_VIA_MANUAL;
1362 }
1363
1364 /**
1365 * Check can delete lp_user_items old
1366 *
1367 * @param LP_Course $course
1368 *
1369 * @return bool
1370 * @throws Exception
1371 * @author tungnx
1372 * @since 4.1.4
1373 * @version 1.0.0
1374 */
1375 public function check_can_delete_item_old( LP_Course $course ): bool {
1376 $can_delete = false;
1377
1378 $lp_user_items_db = LP_User_Items_DB::getInstance();
1379
1380 /**
1381 * For case user buy on frontend (not LP Order manual)
1382 * And course enable repurchase and repurchase_type is keep
1383 */
1384 $allow_repurchase_type = '';
1385
1386 $filter = new LP_User_Items_Filter();
1387 $filter->user_id = get_current_user_id();
1388 $filter->item_id = $course->get_id();
1389 $user_course = $lp_user_items_db->get_last_user_course( $filter );
1390
1391 if ( $user_course && isset( $user_course->user_item_id ) ) {
1392 $latest_user_item_id = $user_course->user_item_id;
1393
1394 /** Get allow_repurchase_type for reset or update. Add in: rest-api/v1/frontend/class-lp-courses-controller.php: purchase_course */
1395 $allow_repurchase_type = learn_press_get_user_item_meta( $latest_user_item_id, '_lp_allow_repurchase_type' );
1396 }
1397
1398 /**
1399 * Course is free
1400 * Or not allow repurchase
1401 * Or repurchase not type 'Keep'
1402 * And else
1403 * Will deleted lp_user_items old
1404 */
1405 if ( $course->is_free() || empty( $allow_repurchase_type ) || ! $course->allow_repurchase() || $allow_repurchase_type != 'keep' ) {
1406 $can_delete = true;
1407 }
1408
1409 return apply_filters( 'learnpress/order/can_delete_old_item', $can_delete, $course );
1410 }
1411
1412 /**
1413 * Get list status of LP Order
1414 *
1415 * @return array
1416 */
1417 public static function get_order_statuses(): array {
1418 $order_statuses = [
1419 LP_ORDER_COMPLETED_DB => LP_ORDER_COMPLETED,
1420 LP_ORDER_PROCESSING_DB => LP_ORDER_PROCESSING,
1421 LP_ORDER_PENDING_DB => LP_ORDER_PENDING,
1422 LP_ORDER_CANCELLED_DB => LP_ORDER_CANCELLED,
1423 LP_ORDER_FAILED_DB => LP_ORDER_FAILED,
1424 ];
1425
1426 return apply_filters( 'lp/order/statuses', $order_statuses );
1427 }
1428
1429 /**
1430 * Query list orders.
1431 *
1432 * @param PostFilter $post_filter
1433 * @param array $param [ 'author' => int, 'post_status' => string|array, 's' => string, 'm' => string, 'posts_per_page' => int, 'paged' => int, 'orderby' => string, 'order' => string ]
1434 *
1435 * @return void
1436 * @throws Exception
1437 * @since 4.3.2.8
1438 * @version 1.0.0
1439 */
1440 public static function handle_params_query_list_orders( PostFilter &$post_filter, array $param = [] ) {
1441 $post_db = PostDB::getInstance();
1442 $user_of_order = absint( $param['author'] ?? 0 );
1443 $status = $param['post_status'] ?? '';
1444 $key = $param['s'] ?? '';
1445 $month = $param['m'] ?? '';
1446 $limit = $param['posts_per_page'] ?? 20;
1447 $paged = $param['paged'] ?? 1;
1448
1449 $order_by = $param['orderby'] ?? 'date';
1450 if ( empty( $order_by ) ) {
1451 $order_by = 'ID';
1452 } else {
1453 switch ( $order_by ) {
1454 case 'date':
1455 $order_by = 'post_date';
1456 break;
1457 case 'title':
1458 $order_by = 'ID';
1459 break;
1460 }
1461 }
1462
1463 $order = $param['order'] ?? 'DESC';
1464 // End convert params
1465
1466 if ( $order_by === 'order_total' ) {
1467 $post_filter->join[] = "INNER JOIN {$post_db->tb_postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_order_total'";
1468 $post_filter->where[] = 'AND CAST(pm2.meta_value AS UNSIGNED)';
1469 $post_filter->order_by = 'pm2.meta_value';
1470 } else {
1471 $post_filter->order_by = $order_by;
1472 }
1473
1474 if ( ! empty( $key ) ) {
1475 $pattern = '/^#\d+$/';
1476 $is_order_id_sure = false;
1477 if ( preg_match( $pattern, $key ) ) {
1478 $is_order_id_sure = true;
1479 $key = str_replace( '#', '', $key );
1480 }
1481
1482 $pattern2 = '#^0+.*\d+$#';
1483 if ( preg_match( $pattern2, $key ) ) {
1484 $key = (int) $key;
1485 }
1486
1487 $key = trim( $key );
1488
1489 if ( $is_order_id_sure ) {
1490 $post_filter->where[] = $post_db->wpdb->prepare( 'AND p.ID = %d', $key );
1491 } else {
1492 $post_filter->join[] = "INNER JOIN {$post_db->tb_lp_order_items} lpori ON p.ID = lpori.order_id";
1493 $post_filter->where[] = $post_db->wpdb->prepare(
1494 'AND (p.ID = %d OR lpori.order_item_name like %s)',
1495 $key,
1496 '%' . $key . '%'
1497 );
1498 }
1499 }
1500
1501 if ( ! empty( $month ) ) {
1502 $year = substr( $month, 0, 4 );
1503 $post_filter->where[] = "AND YEAR(p.post_date) = $year";
1504 if ( strlen( $month ) > 5 ) {
1505 $mon = substr( $month, 4, 2 );
1506 $post_filter->where[] = "AND MONTH(p.post_date) = $mon";
1507 }
1508 }
1509
1510 $post_filter->order = $order;
1511 $post_filter->limit = $limit;
1512 $post_filter->page = $paged;
1513
1514 if ( ! empty( $user_of_order ) ) {
1515 $user_id = absint( $user_of_order );
1516 $post_filter->join[] = "INNER JOIN {$post_db->tb_postmeta} pm1 ON p.ID = pm1.post_id AND pm1.meta_key = '_user_id'";
1517 $post_filter->where[] = "AND ( pm1.meta_value like '%\"$user_id\"%' OR pm1.meta_value = $user_id )";
1518 }
1519
1520 if ( ! empty( $status ) && $status !== 'all' ) {
1521 $post_filter->post_status = (array) $status;
1522 } else {
1523 $post_filter->where[] = $post_db->wpdb->prepare( 'AND p.post_status != %s', LP_ORDER_TRASH );
1524 }
1525 }
1526 }
1527 }
1528