PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.3.7
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.3.7
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.7, at inc/order/class-lp-order.php

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