PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.8
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.8
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
← All changes | inc/order/class-lp-order.php +1847 -1527 4.3.94.4.8 View file →
@@ -1,1527 +1,1847 @@
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 -}
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 + const META_KEY_TRANSACTION_ID = '_transaction_id';
69 + const META_KEY_REFUND_REQUEST = '_lp_refund_request';
70 + const META_KEY_REFUND_REQUEST_REASON = '_lp_refund_request_reason';
71 + const META_KEY_REFUNDED_BY = '_lp_refunded_by';
72 + const META_KEY_REFUNDED_AT = '_lp_refunded_at';
73 + const META_KEY_REFUNDED_AMOUNT = '_lp_refunded_amount';
74 +
75 + /**
76 + * Store order status in transactions.
77 + *
78 + * @var array
79 + */
80 + protected $_status = array();
81 +
82 + /**
83 + * LP_Order constructor.
84 + *
85 + * @param bool $order_id
86 + *
87 + * @throws Exception
88 + */
89 + public function __construct( $order_id = false ) {
90 + $this->_curd = new LP_Order_CURD();
91 + if ( is_numeric( $order_id ) && $order_id > 0 ) {
92 + $this->set_id( $order_id );
93 + } elseif ( $order_id instanceof self ) {
94 + $this->set_id( absint( $order_id->get_id() ) );
95 + } elseif ( ! empty( $order_id->ID ) ) {
96 + $this->set_id( absint( $order_id->ID ) );
97 + }
98 +
99 + if ( $this->get_id() > 0 ) {
100 + $this->load();
101 + }
102 + }
103 +
104 + /**
105 + * Load the order data.
106 + * Check if the id is not zero but it's post type does not exists.
107 + *
108 + * @throws Exception
109 + */
110 + public function load() {
111 + $this->_curd->load( $this );
112 + }
113 +
114 + /**
115 + * Set order date.
116 + *
117 + * @param int|string $date Date time string is time zone of WP setting
118 + */
119 + public function set_order_date( $date ): LP_Order {
120 + $this->set_data_date( 'order_date', $date );
121 +
122 + return $this;
123 + }
124 +
125 + /**
126 + * Get date of this order.
127 + *
128 + * @param string $context
129 + *
130 + * @return string|LP_Datetime
131 + */
132 + public function get_order_date( $context = '' ) {
133 + $date = $this->get_data( 'order_date' );
134 +
135 + if ( 'edit' !== $context ) {
136 +
137 + if ( $date instanceof LP_Datetime ) {
138 + $strtime = strtotime( $date->toSql() );
139 +
140 + switch ( $context ) {
141 + case 'd':
142 + $date = date_i18n( 'Y-m-d', $strtime );
143 + break;
144 + case 'h':
145 + $date = date_i18n( 'H', $strtime );
146 + break;
147 + case 'm':
148 + $date = date_i18n( 'i', $strtime );
149 + break;
150 + case 'timestamp':
151 + $date = $strtime;
152 + break;
153 + default:
154 + $post = get_post( $this->get_id() );
155 + $m_time = $post->post_date;
156 + $time = get_post_time( 'G', true, $post );
157 + $time_diff = time() - $time;
158 +
159 + if ( $time_diff > 0 && $time_diff < DAY_IN_SECONDS ) {
160 + $date = sprintf( __( '%s ago', 'learnpress' ), human_time_diff( $time ) );
161 + } else {
162 + $date = mysql2date( get_option( 'date_format' ), $m_time );
163 + }
164 + }
165 + }
166 + } elseif ( ! $date instanceof LP_Datetime ) {
167 + $date = new LP_Datetime( $date );
168 + }
169 +
170 + return $date;
171 + }
172 +
173 + /**
174 + * Set order key
175 + *
176 + * @param string $order_key
177 + */
178 + public function set_order_key( $order_key ) {
179 + $this->_set_data( 'order_key', $order_key );
180 + }
181 +
182 + /**
183 + * Get order key.
184 + *
185 + * @return array|mixed
186 + */
187 + public function get_order_key() {
188 + return $this->get_data( 'order_key', '' );
189 + }
190 +
191 + /**
192 + * Get confirm received text
193 + *
194 + * @return string
195 + * @since 3.0.0
196 + */
197 + public function get_confirm_order_received_text() {
198 + return apply_filters( 'learn-press/confirm-order-received-text', __( 'Thank you. Your order has been received.', 'learnpress' ), $this->get_id() );
199 + }
200 +
201 + /**
202 + * Get thank you message after the order is placed.
203 + *
204 + * @return mixed
205 + * @since 3.0.0
206 + */
207 + public function get_thankyou_message() {
208 + return apply_filters( 'learn-press/confirm-order-received-text', __( 'Thank you. Your order has been received.', 'learnpress' ), $this->get_id() );
209 + }
210 +
211 + /**
212 + * Checks to see if current order has status as passed.
213 + *
214 + * @param string|array $status String or an array of statuses
215 + *
216 + * @return mixed
217 + */
218 + public function has_status( $status ) {
219 + settype( $status, 'array' );
220 + $has = in_array( $this->get_status(), $status );
221 +
222 + return apply_filters( 'learn-press/has-order-status', $has, $status, $this->get_id() );
223 + }
224 +
225 + /**
226 + * Updates order to new status if needed
227 + *
228 + * @param mixed $new_status
229 + * @param bool $manual Is this a manual order status change?.
230 + *
231 + * @return bool
232 + */
233 + public function update_status( $new_status = 'pending', $manual = false ): bool {
234 + $result = false;
235 +
236 + try {
237 + $this->set_status( $new_status );
238 + $result = $this->save();
239 + } catch ( Throwable $e ) {
240 + error_log( $e->getMessage() );
241 + }
242 +
243 + return $result;
244 + }
245 +
246 + /**
247 + * Format order number id
248 + *
249 + * @return string
250 + */
251 + public function get_order_number(): string {
252 + return learn_press_transaction_order_number( $this->get_id() );
253 + }
254 +
255 + /**
256 + * Get status of the order
257 + *
258 + * @return mixed
259 + */
260 + public function get_order_status() {
261 + return $this->get_status();
262 + }
263 +
264 + public function get_user_ip_address() {
265 + return $this->get_data( 'user_ip_address' );
266 + }
267 +
268 + /**
269 + * Get current status of order
270 + *
271 + * @return mixed
272 + */
273 + public function get_status() {
274 + return $this->get_data( 'status', '' );
275 + }
276 +
277 + /**
278 + * Get label of lp order status
279 + *
280 + * @param string $status
281 + *
282 + * @return string
283 + * @since 4.2.0
284 + * @version 1.0.0
285 + */
286 + public static function get_status_label( string $status ): string {
287 + switch ( $status ) {
288 + case LP_ORDER_COMPLETED:
289 + $status = __( 'Completed', 'learnpress' );
290 + break;
291 + case LP_ORDER_PENDING:
292 + $status = __( 'Pending', 'learnpress' );
293 + break;
294 + case LP_ORDER_PROCESSING:
295 + $status = __( 'Processing', 'learnpress' );
296 + break;
297 + case LP_ORDER_CANCELLED:
298 + $status = __( 'Cancelled', 'learnpress' );
299 + break;
300 + case LP_ORDER_FAILED:
301 + $status = __( 'Failed', 'learnpress' );
302 + break;
303 + case LP_ORDER_TRASH:
304 + $status = __( 'Trash', 'learnpress' );
305 + break;
306 + case 'on-hold':
307 + $status = __( 'On hold', 'learnpress' );
308 + break;
309 + case LP_ORDER_REFUNDED:
310 + $status = __( 'Refunded', 'learnpress' );
311 + break;
312 + default:
313 + $status = '';
314 + break;
315 + }
316 +
317 + if ( ! is_string( $status ) ) {
318 + $status = '';
319 + }
320 +
321 + return $status;
322 + }
323 +
324 + /**
325 + * Get icons of lp order status
326 + *
327 + * @return array
328 + * @since 4.2.0
329 + * @version 1.0.0
330 + */
331 + public static function get_icons_status(): array {
332 + $icons = array(
333 + LP_ORDER_COMPLETED => "<i class='dashicons dashicons-yes-alt'></i>",
334 + LP_ORDER_PENDING => "<i class='dashicons dashicons-flag'></i>",
335 + LP_ORDER_PROCESSING => "<i class='dashicons dashicons-clock'></i>",
336 + LP_ORDER_CANCELLED => "<i class='dashicons dashicons-dismiss'></i>",
337 + LP_ORDER_FAILED => "<i class='dashicons dashicons-warning'></i>",
338 + LP_ORDER_REFUNDED => "<i class='dashicons dashicons-undo'></i>",
339 + );
340 +
341 + return apply_filters( 'lp/order/status/icons', $icons );
342 + }
343 +
344 + /**
345 + * Set order status.
346 + * $new_status shouldn't have prefix 'lp-'
347 + *
348 + * @param string $new_status
349 + * @param string $note - Optional. Note for changing status.
350 + *
351 + * @sicne 3.0.0
352 + * @version 1.0.1
353 + */
354 + public function set_status( string $new_status = '', string $note = '' ) {
355 + // Ensure status not has prefix 'lp-'.
356 + $new_status = str_replace( 'lp-', '', $new_status );
357 + $valid_statuses = array_values( self::get_order_statuses() );
358 + if ( ! in_array( $new_status, $valid_statuses ) && 'trash' !== $new_status ) {
359 + $new_status = LP_ORDER_PENDING;
360 + }
361 +
362 + $this->_set_data( 'status', $new_status );
363 + }
364 +
365 + public function get_order_status_html() {
366 + $order_status = self::get_status_label( $this->get_status() );
367 + $status = ucfirst( $order_status );
368 + $class = 'order-status order-status-' . sanitize_title( $status );
369 + $html = sprintf( '<span class="%s">%s</span>', apply_filters( 'learn_press_order_status_class', $class, $status, $this ), $status );
370 +
371 + return apply_filters( 'learn_press_order_status_html', $html, $this );
372 + }
373 +
374 + /**
375 + * Mark order as complete
376 + *
377 + * @param string $transaction_id
378 + *
379 + * @return bool
380 + */
381 + public function payment_complete( $transaction_id = '' ): bool {
382 + do_action( 'learn-press/payment-pre-complete', $this->get_id() );
383 +
384 + $valid_order_statuses = apply_filters(
385 + 'learn-press/valid-order-statuses-for-payment-complete',
386 + array(
387 + 'pending',
388 + 'processing',
389 + ),
390 + $this
391 + );
392 +
393 + if ( $this->get_id() && $this->has_status( $valid_order_statuses ) ) {
394 +
395 + $this->update_status( 'completed' );
396 +
397 + if ( ! empty( $transaction_id ) ) {
398 + add_post_meta( $this->get_id(), '_transaction_id', $transaction_id, true );
399 + }
400 +
401 + do_action( 'learn-press/payment-complete', $this->get_id() );
402 + } else {
403 + do_action( 'learn-press/payment-complete-order-status-' . $this->get_status(), $this->get_id() );
404 + }
405 +
406 + return true;
407 + }
408 +
409 + /**
410 + * Get checkout order successful url.
411 + *
412 + * @return string
413 + */
414 + public function get_checkout_order_received_url() {
415 + $received_url = learn_press_get_endpoint_url( 'lp-order-received', $this->get_id(), learn_press_get_page_link( 'checkout' ) );
416 +
417 + $received_url = add_query_arg( 'key', $this->get_order_key(), $received_url );
418 +
419 + $received_url = apply_filters( 'learn_press_get_checkout_order_received_url', $received_url, $this );
420 +
421 + /**
422 + * @since 3.0.0
423 + */
424 + return apply_filters( 'learn-press/checkout-order-received-url', $received_url, $this );
425 + }
426 +
427 + /*********************************/
428 +
429 + /**
430 + * Get customer name of the order
431 + */
432 + public function get_customer_name() {
433 + $customer_name = '';
434 +
435 + if ( 'auto-draft' !== get_post_status( $this->get_id() ) ) {
436 + $user_ids = $this->get_users();
437 + if ( ! empty( $user_ids ) ) {
438 + $customer_name = array();
439 + foreach ( $user_ids as $uid ) {
440 + $customer = learn_press_get_user( $uid );
441 + if ( $customer && $customer->is_exists() ) {
442 + if ( $customer->get_data( 'display_name' ) ) {
443 + $customer_name[] = $customer->get_data( 'display_name' );
444 + } elseif ( $customer->get_data( 'user_nicename' ) ) {
445 + $customer_name[] = $customer->get_data( 'user_nicename' );
446 + } elseif ( $customer->get_data( 'user_login' ) ) {
447 + $customer_name[] = $customer->get_data( 'user_login' );
448 + }
449 + } else {
450 + $customer_name[] = $this->get_guest_customer_name();
451 + }
452 + }
453 +
454 + $customer_name = join( ', ', $customer_name );
455 + }
456 + }
457 +
458 + if ( ! $customer_name ) {
459 + $customer_name = $this->get_guest_customer_name();
460 + }
461 +
462 + return $customer_name;
463 + }
464 +
465 + public function get_guest_customer_name() {
466 + $checkout_email = $this->get_checkout_email();
467 + if ( $checkout_email ) {
468 + $customer_name = sprintf( __( '%s (Guest)', 'learnpress' ), $checkout_email );
469 + } else {
470 + $customer_name = sprintf( __( '(Guest)', 'learnpress' ), $checkout_email );
471 + }
472 +
473 + return apply_filters( 'learn-press/order/guest-customer-name', $customer_name );
474 + }
475 +
476 + /*
477 + public function customer_exists() {
478 + return false !== get_userdata( $this->get_data( 'user_id' ) );
479 + }*/
480 +
481 + /**
482 + * Get items of the order only to show.
483 + *
484 + * @return mixed
485 + */
486 + public function get_items() {
487 + $items = $this->_curd->read_items( $this );
488 +
489 + return apply_filters( 'learn-press/order-items', $items );
490 + }
491 +
492 + /**
493 + * Get all items of the order.
494 + * Only use for add/remove items to learn (learn_press_user_items) table.
495 + * Where call this function, must be careful, must set_time_limit( 0 );.
496 + *
497 + * @return array|null
498 + * @version 1.0.1
499 + * @since 4.2.7.2
500 + */
501 + public function get_all_items() {
502 + global $wpdb;
503 + $lp_order_items = LP_Database::getInstance();
504 + $table_order_items = $lp_order_items->tb_lp_order_items;
505 +
506 + $query = $wpdb->prepare(
507 + "SELECT order_item_id, order_item_name, item_id, item_type
508 + From $table_order_items
509 + WHERE order_id = %d",
510 + $this->get_id()
511 + );
512 +
513 + return $wpdb->get_results( $query, ARRAY_A );
514 + }
515 +
516 + /**
517 + * Get items of the order by filter
518 + *
519 + * @param array $filter
520 + *
521 + * @return mixed
522 + * @author tungnx
523 + */
524 + public function get_items_filter( $filter = array() ) {
525 + $items = $this->_curd->read_items_filter( $this, $filter );
526 +
527 + return apply_filters( 'learn-press/order-items', $items );
528 + }
529 +
530 + public function is_child() {
531 + return $this->get_parent_id();
532 + }
533 +
534 + public function get_parent() {
535 + return $this->get_parent_id() ? learn_press_get_order( $this->get_parent_id() ) : false;
536 + }
537 +
538 + /**
539 + * Get list of course ids from order.
540 + *
541 + * @return array|bool
542 + * @editor tungnx
543 + */
544 + public function get_item_ids() {
545 + $items = $this->get_all_items();
546 +
547 + if ( $items ) {
548 + $course_ids = array();
549 + foreach ( $items as $item ) {
550 + if ( $item['item_type'] === LP_COURSE_CPT ) {
551 + $course_ids[] = (int) $item['item_id'];
552 + }
553 + }
554 +
555 + return $course_ids;
556 + /**
557 + * Comment by tungnx. Because it will error if item didn't have key 'course_id'.
558 + * Ex: case buy certificate, will not have that key
559 + */
560 + // return @wp_list_pluck( $items, 'course_id' );
561 + }
562 +
563 + return false;
564 + }
565 +
566 + /**
567 + * Check is order of Guest (user not login)
568 + *
569 + * @return bool
570 + */
571 + public function is_guest(): bool {
572 + return ! get_user_by( 'ID', $this->get_user_id() );
573 + }
574 +
575 + public function get_item_meta( &$item ) {
576 + $metas = get_metadata( 'learnpress_order_item', $item['id'] );
577 + if ( $metas ) {
578 + foreach ( $metas as $k => $v ) {
579 + $item[ preg_replace( '!^_!', '', $k ) ] = LP_Helper::maybe_unserialize( $v[0] );
580 + }
581 + }
582 + }
583 +
584 + /**
585 + * Remove all items from an order
586 + */
587 + public function remove_order_items() {
588 + global $wpdb;
589 + $wpdb->query(
590 + $wpdb->prepare(
591 + "
592 + DELETE FROM itemmeta
593 + USING $wpdb->learnpress_order_itemmeta itemmeta
594 + INNER JOIN $wpdb->learnpress_order_items items
595 + WHERE itemmeta.learnpress_order_item_id = items.order_item_id
596 + AND items.order_id = %d",
597 + $this->get_id()
598 + )
599 + );
600 + $wpdb->query(
601 + $wpdb->prepare(
602 + "
603 + DELETE FROM {$wpdb->learnpress_order_items}
604 + WHERE order_id = %d",
605 + $this->get_id()
606 + )
607 + );
608 + $wpdb->query( $wpdb->prepare( "ALTER TABLE {$wpdb->learnpress_order_itemmeta} AUTO_INCREMENT = %d", 1 ) );
609 + $wpdb->query( $wpdb->prepare( "ALTER TABLE {$wpdb->learnpress_order_items} AUTO_INCREMENT = %d", 1 ) );
610 + }
611 +
612 + /**
613 + * Add a new item to order.
614 + *
615 + * @param array|int $item
616 + *
617 + * @return int
618 + * @throws Exception
619 + * @since 1.0.0
620 + * @version 4.2.5
621 + */
622 + public function add_item( $item ): int {
623 + global $wpdb;
624 + $order_item_id = 0;
625 +
626 + try {
627 + if ( is_numeric( $item ) ) {
628 + $item = array(
629 + 'item_id' => absint( $item ),
630 + 'order_item_name' => get_the_title( $item ),
631 + );
632 + }
633 +
634 + $item_type = $item['item_type'] ?? get_post_type( $item['item_id'] );
635 + if ( ! in_array( $item_type, learn_press_get_item_types_can_purchase() ) ) {
636 + return false;
637 + }
638 +
639 + $item = wp_parse_args(
640 + $item,
641 + array(
642 + 'item_id' => 0,
643 + 'item_type' => '',
644 + 'order_item_name' => '',
645 + 'quantity' => 1,
646 + 'subtotal' => 0,
647 + 'total' => 0,
648 + 'meta' => array(),
649 + )
650 + );
651 +
652 + switch ( $item_type ) {
653 + case LP_COURSE_CPT:
654 + $course = learn_press_get_course( $item['item_id'] );
655 + $item['subtotal'] = apply_filters( 'learnpress/order/item/subtotal', $course->get_price() * $item['quantity'], $course, $item, $this );
656 + $item['total'] = apply_filters( 'learnpress/order/item/total', $course->get_price() * $item['quantity'], $course, $item, $this );
657 + $item['order_item_name'] = apply_filters( 'learnpress/order/item/title', get_post_field( 'post_title', $item['item_id'], 'raw' ), $course, $item, $this );
658 + $item['meta']['_course_id'] = $item['item_id'];
659 + break;
660 + default:
661 + $item = apply_filters( 'learnpress/order/add-item/item_type_' . $item_type, $item );
662 + break;
663 + }
664 +
665 + // Insert new order item
666 + $wpdb->insert(
667 + $wpdb->learnpress_order_items,
668 + array(
669 + 'order_item_name' => $item['order_item_name'],
670 + 'order_id' => $this->get_id(),
671 + 'item_id' => $item['item_id'],
672 + 'item_type' => $item_type,
673 + ),
674 + array(
675 + '%s',
676 + '%d',
677 + '%d',
678 + '%s',
679 + )
680 + );
681 + $order_item_id = absint( $wpdb->insert_id );
682 + // Clear cache
683 + $key = "order/{$this->get_id()}/{$this->get_status()}/items";
684 + LP_Cache::cache_load_first( 'clear', $key );
685 + // End insert new order item
686 +
687 + // Add learnpress_order_itemmeta
688 + $item['meta']['_quantity'] = $item['quantity'] ?? 1;
689 + $item['meta']['_subtotal'] = $item['subtotal'] ?? 0;
690 + $item['meta']['_total'] = $item['total'] ?? 0;
691 +
692 + if ( is_array( $item['meta'] ) ) {
693 + foreach ( $item['meta'] as $k => $v ) {
694 + learn_press_add_order_item_meta( $order_item_id, $k, $v );
695 + }
696 + }
697 +
698 + do_action( 'learn-press/added-order-item-data', $order_item_id, $item, $this->get_id() );
699 + } catch ( Throwable $e ) {
700 + error_log( __FUNCTION__ . ': ' . $e->getMessage() );
701 + }
702 +
703 + return $order_item_id;
704 + }
705 +
706 + /**
707 + * Set total
708 + *
709 + * @param int|float $total
710 + */
711 + public function set_total( $total = 0 ) {
712 + $this->_set_data( 'total', $total );
713 + }
714 +
715 + /**
716 + * Get total
717 + *
718 + * @return int|float
719 + */
720 + public function get_total() {
721 + return $this->get_data( 'total', 0 );
722 + }
723 +
724 + /**
725 + * @param float|int $subtotal
726 + */
727 + public function set_subtotal( $subtotal = 0 ) {
728 + $this->_set_data( 'subtotal', $subtotal );
729 + }
730 +
731 + /**
732 + * Get subtotal
733 + *
734 + * @return float
735 + */
736 + public function get_subtotal() {
737 + return $this->get_data( 'subtotal' );
738 + }
739 +
740 + /**
741 + * Remove an item from database and it's data.
742 + *
743 + * @param $order_item_id
744 + *
745 + * @return bool
746 + * @throws Exception
747 + */
748 + public function remove_item( $order_item_id ): bool {
749 + global $wpdb;
750 +
751 + $item_id = absint( $order_item_id );
752 + $order_item_id = absint( $order_item_id );
753 +
754 + if ( ! $item_id ) {
755 + return false;
756 + }
757 +
758 + do_action( 'learn_press_before_delete_order_item', $item_id );
759 +
760 + /**
761 + * @since 3.0.0
762 + */
763 + do_action( 'learn-press/before-delete-order-item', $item_id, $this->get_id() );
764 +
765 + $filter = new OrderItemsFilter();
766 + $filter->order_item_id = $order_item_id;
767 + $filter->return_string_query = true;
768 + $filter->run_query_count = false;
769 + $order_items_db = LPOrderItemsDB::getInstance();
770 + $order_items_db->get_query_single_row( $filter );
771 + $query_single_row = $order_items_db->get_items( $filter );
772 + $itemObj = $order_items_db->wpdb->get_row( $query_single_row );
773 +
774 + if ( $itemObj && $itemObj->item_type === LP_COURSE_CPT ) {
775 + $course_id = $itemObj->item_id;
776 + $user_ids = $this->get_user_id();
777 + if ( is_array( $user_ids ) ) {
778 + foreach ( $user_ids as $user_id ) {
779 + // Delete course item on learnpress_user_items if exists
780 + $userCourseItem = UserItemModel::find_user_item(
781 + $user_id,
782 + $course_id,
783 + LP_COURSE_CPT,
784 + $this->get_id(),
785 + LP_ORDER_CPT,
786 + true
787 + );
788 + if ( $userCourseItem instanceof UserItemModel ) {
789 + $userCourseModel = new UserCourseModel( $userCourseItem );
790 + $userCourseModel->delete();
791 + }
792 + }
793 + } else {
794 + // Delete course item on learnpress_user_items if exists
795 + $userCourseItem = UserItemModel::find_user_item(
796 + $user_ids,
797 + $course_id,
798 + LP_COURSE_CPT,
799 + $this->get_id(),
800 + LP_ORDER_CPT,
801 + true
802 + );
803 + if ( $userCourseItem instanceof UserItemModel ) {
804 + $userCourseModel = new UserCourseModel( $userCourseItem );
805 + $userCourseModel->delete();
806 + }
807 + }
808 + }
809 +
810 + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}learnpress_order_items WHERE order_item_id = %d", $item_id ) );
811 + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}learnpress_order_itemmeta WHERE learnpress_order_item_id = %d", $item_id ) );
812 +
813 + // Clear cache
814 + $key = "order/{$this->get_id()}/{$this->get_status()}/items";
815 + LP_Cache::cache_load_first( 'clear', $key );
816 +
817 + /**
818 + * @since 3.0.0
819 + */
820 + do_action( 'learn-press/deleted-order-item', $item_id, $this->get_id() );
821 +
822 + do_action( 'learn_press_delete_order_item', $item_id );
823 +
824 + return true;
825 + }
826 +
827 + /**
828 + * Add multiple items.
829 + *
830 + * @param array $items
831 + *
832 + * @return array
833 + */
834 + public function add_items( $items ) {
835 + settype( $items, 'array' );
836 + $item_ids = array();
837 + try {
838 + foreach ( $items as $item ) {
839 + $item_id = $this->add_item( $item );
840 + if ( $item_id ) {
841 + $item_ids[] = $item_id;
842 + }
843 + }
844 + } catch ( Throwable $e ) {
845 + error_log( $e->getMessage() );
846 + }
847 +
848 + return $item_ids;
849 + }
850 +
851 + /**
852 + * @param string $field
853 + *
854 + * @return array|bool|int|LP_User|mixed
855 + */
856 + public function get_user( $field = '' ) {
857 +
858 + $users = $this->get_users();
859 + $uid = reset( $users );
860 +
861 + $user = learn_press_get_user( $uid );
862 + if ( false === $user ) {
863 + return false;
864 + }
865 +
866 + if ( strtolower( $field ) == 'id' ) {
867 + return $user->get_id();
868 + }
869 +
870 + if ( $field && $user ) {
871 + return $user->get_data( $field );
872 + }
873 +
874 + return $user;
875 + }
876 +
877 + /**
878 + * Get user id in array.
879 + * user_id = 0 -> User type Guest
880 + *
881 + * @return int[]
882 + * @editor tungnx
883 + * @modify 4.1.4
884 + * @version 1.0.1
885 + */
886 + public function get_users(): array {
887 + $users = $this->get_user_id();
888 + if ( $users !== - 1 ) {
889 + settype( $users, 'array' );
890 +
891 + $users = array_map(
892 + function ( $user ) {
893 + return absint( $user );
894 + },
895 + $users
896 + );
897 +
898 + $users = array_unique( $users );
899 + } else {
900 + $users = array();
901 + }
902 +
903 + return $users;
904 + }
905 +
906 + public function dropdown_users() {
907 + $order_users = $this->get_users();
908 + $users = get_users( array() );
909 + echo '<select name="order-customer[]" id="order-customer" multiple="multiple">';
910 + foreach ( (array) $users as $user ) {
911 + // $user->get_id() = (int) $user->get_id();
912 + if ( in_array( $user->get_id(), $order_users ) ) {
913 + $found_selected = true;
914 + } else {
915 + $found_selected = false;
916 + }
917 + printf( '<option value="%d" %s>%s</option>', esc_attr( $user->get_id() ), selected( $found_selected, true, false ), esc_html( $user->user_login ) );
918 + }
919 + echo '</select>';
920 + }
921 +
922 +
923 + public function get_checkout_payment_url() {
924 + }
925 +
926 + public function get_formatted_order_subtotal() {
927 + $currency_symbol = learn_press_get_currency_symbol( $this->get_currency() );
928 +
929 + return learn_press_format_price( $this->get_subtotal(), $currency_symbol );
930 + }
931 +
932 + public function get_formatted_order_total() {
933 + $currency_symbol = learn_press_get_currency_symbol( $this->get_currency() );
934 +
935 + return learn_press_format_price( $this->get_total(), $currency_symbol );
936 + }
937 +
938 + public function get_currency() {
939 + return $this->get_data( 'currency' ) ? $this->get_data( 'currency' ) : learn_press_get_currency();
940 + }
941 +
942 + public function set_currency( $value ) {
943 + $this->_set_data( 'currency', $value );
944 + }
945 +
946 + /**
947 + * Get payment method title.
948 + *
949 + * @return array|mixed
950 + */
951 + public function get_payment_method_title() {
952 + return esc_html( $this->get_data( 'payment_method_title', '' ) );
953 + }
954 +
955 + /**
956 + * Get view order detail url.
957 + *
958 + * @return string
959 + */
960 + public function get_view_order_url() {
961 + global $wp_query;
962 +
963 + $userModel = UserModel::find( get_current_user_id(), true );
964 + $view_order_endpoint = LP_Settings::instance()->get( 'profile_endpoints.order-details' );
965 +
966 + if ( ! $view_order_endpoint ) {
967 + $view_order_endpoint = 'order-details';
968 + }
969 +
970 + if ( ! $userModel ) {
971 + return '';
972 + }
973 +
974 + $user_slug = $userModel->user_nicename;
975 + $link_profile = learn_press_get_page_link( 'profile' );
976 +
977 + $view_order_endpoint = urlencode( $view_order_endpoint );
978 + if ( get_option( 'permalink_structure' ) ) {
979 + $view_order_url = $link_profile . $user_slug . '/' . $view_order_endpoint . '/' . $this->get_id() . '/';
980 + } else {
981 + $args = array(
982 + 'user' => $user_slug,
983 + );
984 + $args['view'] = $view_order_endpoint;
985 +
986 + if ( $view_order_endpoint ) {
987 + $args['id'] = $this->get_id();
988 + }
989 + $view_order_url = add_query_arg(
990 + $args,
991 + $link_profile
992 + );
993 + }
994 +
995 + return apply_filters( 'learn_press_view_order_url', $view_order_url, $this );
996 + }
997 +
998 + /**
999 + * Get cancel url if it's status is pending.
1000 + *
1001 + * @param bool $force
1002 + *
1003 + * @return mixed
1004 + */
1005 + public function get_cancel_order_url( $force = false ) {
1006 +
1007 + $url = false;
1008 + if ( $this->has_status( 'pending' ) ) {
1009 + $user_id = get_current_user_id();
1010 + $profile = LP_Profile::instance( $user_id );
1011 + $url = $profile->get_tab_link( LP_Settings::instance()->get( 'profile_endpoints.orders' ) );
1012 + if ( ! $force ) {
1013 + $url = esc_url_raw( add_query_arg( 'cancel-order', $this->get_id(), $url ) );
1014 + } else {
1015 + $url = esc_url_raw( add_query_arg( 'cancelled-order', $this->get_id(), $url ) );
1016 + }
1017 +
1018 + $url = wp_nonce_url( $url, 'cancel-order', 'lp-nonce' );
1019 + }
1020 +
1021 + return apply_filters( 'learn-press/order-cancel-url', $url, $this->get_id() );
1022 + }
1023 +
1024 + /**
1025 + * Get refund URL if order can be refunded by customer.
1026 + *
1027 + * @since 4.3.4
1028 + * @version 1.0.0
1029 + * @return bool|string
1030 + */
1031 + /*public function get_refund_order_url() {
1032 + $url = false;
1033 + $user = learn_press_get_current_user();
1034 + if ( ! $user instanceof LP_User || $user->get_id() <= 0 ) {
1035 + return apply_filters( 'learn-press/order-refund-url', $url, $this->get_id() );
1036 + }
1037 +
1038 + $eligibility = learn_press_get_order_refund_eligibility( $this, $user->get_id() );
1039 + if ( empty( $eligibility['eligible'] ) ) {
1040 + return apply_filters( 'learn-press/order-refund-url', $url, $this->get_id() );
1041 + }
1042 +
1043 + $url = learn_press_user_profile_link(
1044 + $user->get_id(),
1045 + LP_Settings::instance()->get( 'profile_endpoints.orders', 'orders' )
1046 + );
1047 + $url = esc_url_raw( $url );
1048 +
1049 + return apply_filters( 'learn-press/order-refund-url', $url, $this->get_id() );
1050 + }*/
1051 +
1052 + /**
1053 + * Get profile order's actions.
1054 + *
1055 + * @return array|mixed
1056 + */
1057 + public function get_profile_order_actions() {
1058 + $actions = [];
1059 +
1060 + $userModel = UserModel::find( get_current_user_id(), true );
1061 + if ( ! $userModel instanceof UserModel ) {
1062 + return $actions;
1063 + }
1064 +
1065 + $order_user_id = $this->get_users();
1066 + if ( ! in_array( $userModel->get_id(), $order_user_id ) ) {
1067 + return $actions;
1068 + }
1069 +
1070 + $actions = array(
1071 + 'view' => array(
1072 + 'url' => $this->get_view_order_url(),
1073 + 'text' => __( 'View', 'learnpress' ),
1074 + ),
1075 + );
1076 +
1077 + $cancel_url = $this->get_cancel_order_url();
1078 + if ( $cancel_url ) {
1079 + $actions['cancel'] = array(
1080 + 'url' => $this->get_cancel_order_url(),
1081 + 'text' => __( 'Cancel', 'learnpress' ),
1082 + );
1083 + }
1084 +
1085 + $refund_request_status = $this->get_refund_request();
1086 + $can_send_request_refund = $this->can_send_request_refund( $userModel );
1087 + if ( 'pending' === $refund_request_status ) {
1088 + $actions['refund-requested'] = array(
1089 + 'url' => '',
1090 + 'text' => __( 'Refund Requested', 'learnpress' ),
1091 + );
1092 + } elseif ( $can_send_request_refund === true ) {
1093 + $require_reason = 'yes' === learn_press_get_refund_setting( 'require_refund_reason', 'no' );
1094 +
1095 + $actions['refund'] = array(
1096 + 'url' => learn_press_user_profile_link(
1097 + $userModel->get_id(),
1098 + LP_Settings::instance()->get( 'profile_endpoints.orders', 'orders' )
1099 + ),
1100 + 'text' => __( 'Refund', 'learnpress' ),
1101 + 'class' => 'lp-refund-order-action',
1102 + 'data' => array(
1103 + 'order_id' => $this->get_id(),
1104 + 'require_reason' => $require_reason ? 'yes' : 'no',
1105 + 'reason_prompt' => __( 'Enter your refund reason', 'learnpress' ),
1106 + 'reason_placeholder' => __( 'Please describe why you want a refund.', 'learnpress' ),
1107 + 'reason_required' => __( 'Refund reason is required.', 'learnpress' ),
1108 + 'confirm_title' => __( 'Request a refund?', 'learnpress' ),
1109 + 'confirm_text' => __( 'This request will be sent and processed according to payment settings.', 'learnpress' ),
1110 + 'confirm_button' => __( 'Submit Request', 'learnpress' ),
1111 + 'cancel_button' => __( 'Cancel', 'learnpress' ),
1112 + ),
1113 + );
1114 + }
1115 +
1116 + $actions = apply_filters( 'learn-press/profile-order-actions', $actions, $this->get_id() );
1117 +
1118 + return $actions;
1119 + }
1120 +
1121 + public function add_note( $note = null ) {
1122 + $comment_author = '';
1123 + $comment_author_email = '';
1124 + if ( is_user_logged_in() ) {
1125 + $user = get_user_by( 'id', get_current_user_id() );
1126 + $comment_author = $user->display_name;
1127 + $comment_author_email = $user->user_email;
1128 + }
1129 +
1130 + $comment_post_ID = $this->get_id();
1131 + $comment_author_url = '';
1132 + $comment_content = $note;
1133 + $comment_agent = 'LearnPress';
1134 + $comment_type = 'lp_order_note';
1135 + $comment_parent = 0;
1136 + $comment_approved = 1;
1137 +
1138 + $commentdata = apply_filters(
1139 + 'learn_press_new_order_note_data',
1140 + compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_agent', 'comment_type', 'comment_parent', 'comment_approved' ),
1141 + $this->get_id()
1142 + );
1143 +
1144 + return wp_insert_comment( $commentdata );
1145 + }
1146 +
1147 + /**
1148 + * Get user_name
1149 + *
1150 + * @param int $user_id
1151 + *
1152 + * @return string
1153 + */
1154 + public function get_user_name( int $user_id = 0 ): string {
1155 + $user = learn_press_get_user( $user_id );
1156 +
1157 + if ( $user && ! $user instanceof LP_User_Guest ) {
1158 + $user_name = $user->get_display_name();
1159 + } else {
1160 + $user_name = $this->get_checkout_email();
1161 + }
1162 +
1163 + return apply_filters( 'learn-press/order/user-name', sprintf( _x( '%1$s', 'full name', 'learnpress' ), $user_name ) );
1164 + }
1165 +
1166 + /**
1167 + * Check to see if this order is for multi users
1168 + *
1169 + * @return bool
1170 + * @since 2.1.5
1171 + */
1172 + public function is_multi_users(): bool {
1173 + return is_array( $this->get_user_id() );
1174 + }
1175 +
1176 + /**
1177 + * Get email of user has bought this order.
1178 + * In case this order is for multi users return an array with multi email addresses.
1179 + *
1180 + * @return array
1181 + * @since 2.1.5
1182 + */
1183 + public function get_user_data(): array {
1184 + $data = array();
1185 +
1186 + $user_ids = $this->get_users();
1187 + if ( ! empty( $user_ids ) ) {
1188 + foreach ( $user_ids as $user_id ) {
1189 + $user = learn_press_get_user( $user_id );
1190 + if ( $user && $user->is_exists() ) {
1191 + $data[ $user_id ] = $user->get_data(
1192 + array(
1193 + 'id',
1194 + 'email',
1195 + 'user_login',
1196 + 'description',
1197 + 'first_name',
1198 + 'last_name',
1199 + 'nickname',
1200 + 'display_name',
1201 + )
1202 + );
1203 + }
1204 + }
1205 + }
1206 +
1207 + return $data;
1208 + }
1209 +
1210 + /**
1211 + * Get email's user by user_id
1212 + *
1213 + * @param int $user_id
1214 + *
1215 + * @return string
1216 + * @editor tungnx
1217 + * @modify 4.1.3
1218 + * @version 1.0.3
1219 + */
1220 + public function get_user_email( int $user_id = 0 ): string {
1221 + if ( $user_id == 0 ) {
1222 + $user_id = (int) $this->get_user_id();
1223 + }
1224 +
1225 + $userModel = UserModel::find( $user_id, true );
1226 + if ( $userModel instanceof UserModel ) {
1227 + $email = $userModel->get_email();
1228 + } else {
1229 + $email = $this->get_checkout_email();
1230 + }
1231 +
1232 + return $email;
1233 + }
1234 +
1235 + /**
1236 + * Order title
1237 + *
1238 + * @param string $context
1239 + *
1240 + * @return array|mixed
1241 + */
1242 + public function get_title( $context = '' ) {
1243 + return $this->get_data( 'order_title', __( 'Order on', 'learnpress' ) . ' ' . current_time( 'l jS F Y h:i:s A' ) );
1244 + }
1245 +
1246 + public function get_parent_id() {
1247 + return $this->get_data( 'parent_id', 0 );
1248 + }
1249 +
1250 + public function set_parent_id( $parent_id ) {
1251 + $this->_set_data( 'parent_id', $parent_id );
1252 + }
1253 +
1254 + public function set_user_id( $user_id ) {
1255 + $this->_set_data( 'user_id', $user_id );
1256 + }
1257 +
1258 + /**
1259 + * Set 1 for is user guest
1260 + *
1261 + * @param int $user_type_guest
1262 + */
1263 + public function set_user_type_guest( int $user_type_guest ) {
1264 + $this->_set_data( 'user_type_guest', $user_type_guest );
1265 + }
1266 +
1267 + /**
1268 + * Get user's ids of order.
1269 + *
1270 + * @return array|int
1271 + */
1272 + public function get_user_id() {
1273 + return $this->get_data( 'user_id', - 1 );
1274 + }
1275 +
1276 + /**
1277 + * Get date modified of order.
1278 + *
1279 + * @return LP_Datetime
1280 + */
1281 + public function get_date_modified() {
1282 + return $this->get_data( 'date_modified' );
1283 + }
1284 +
1285 + /**
1286 + * Set date modified of order.
1287 + *
1288 + * @param mixed $date
1289 + */
1290 + public function set_date_modified( $date ) {
1291 + $this->_set_data( 'date_modified', $date );
1292 + }
1293 +
1294 + /**
1295 + * Set method for creating the order, such as: checkout
1296 + *
1297 + * @param string $created_via
1298 + */
1299 + public function set_created_via( $created_via ) {
1300 + $this->_set_data( 'created_via', $created_via );
1301 + }
1302 +
1303 + /**
1304 + * Get method which order is created, such as: checkout
1305 + *
1306 + * @return string
1307 + */
1308 + public function get_created_via(): string {
1309 + return $this->get_data( 'created_via', '' );
1310 + }
1311 +
1312 + /**
1313 + * Save order data.
1314 + *
1315 + * @return mixed
1316 + *
1317 + * @throws Exception
1318 + */
1319 + public function save() {
1320 + $old_status = '';
1321 + $is_created = ! $this->get_id();
1322 +
1323 + if ( $this->get_id() ) {
1324 + $old_status_post = get_post_status( $this->get_id() );
1325 + if ( ! in_array( $old_status_post, array( 'trash', 'auto-draft' ) ) ) {
1326 + $old_status = str_replace( 'lp-', '', $old_status_post );
1327 + } else {
1328 + $old_status = $old_status_post;
1329 + }
1330 +
1331 + $return = $this->_curd->update( $this );
1332 + } else {
1333 + $return = $this->_curd->create( $this );
1334 + $this->_id = $return;
1335 + }
1336 +
1337 + $new_status_post = get_post_status( $this->get_id() );
1338 + if ( ! in_array( $new_status_post, array( 'trash', 'auto-draft' ) ) ) {
1339 + $new_status = str_replace( 'lp-', '', $new_status_post );
1340 + } else {
1341 + $new_status = $new_status_post;
1342 + }
1343 +
1344 + $order_id = $this->get_id();
1345 +
1346 + if ( $is_created && $order_id ) {
1347 + do_action( 'learn-press/order/created', $order_id, $this );
1348 + }
1349 +
1350 + if ( $new_status !== $old_status ) {
1351 + do_action( 'learn-press/order/status-changed', $order_id, $old_status, $new_status );
1352 + do_action( 'learn-press/order/status-' . $new_status, $order_id, $old_status );
1353 + do_action( 'learn-press/order/status-' . $old_status . '-to-' . $new_status, $order_id );
1354 + }
1355 +
1356 + // $this->_save_status();
1357 +
1358 + return $return;
1359 + }
1360 +
1361 + /**
1362 + * Get an instance of LP_Order by post ID or WP_Post object
1363 + *
1364 + * @param $order
1365 + * @param $force
1366 + *
1367 + * @return LP_Order
1368 + */
1369 + public static function instance( $order, $force = true ) {
1370 + // learn_press_deprecated_function( 'new LP_Order', '3.0', 'learn_press_get_order' );
1371 +
1372 + return learn_press_get_order( $order );
1373 + }
1374 +
1375 + /** Getter/Setter **/
1376 + public function set_customer_message( $message ) {
1377 + $this->_set_data( 'customer_message', $message );
1378 + }
1379 +
1380 + public function set_customer_note( $note ) {
1381 + $this->_set_data( 'customer_note', $note );
1382 + }
1383 +
1384 + public function get_customer_note() {
1385 + return $this->get_data( 'customer_note' ) . '';
1386 + }
1387 +
1388 + public function set_user_ip_address( $value ) {
1389 + $this->_set_data( 'user_ip_address', $value );
1390 + }
1391 +
1392 + public function set_user_agent( $value ) {
1393 + $this->_set_data( 'user_agent', $value );
1394 + }
1395 +
1396 + public function get_user_agent() {
1397 + return $this->get_data( 'user_agent' );
1398 + }
1399 +
1400 + public function set_checkout_email( $email ) {
1401 + $this->_set_data( 'checkout_email', $email );
1402 + }
1403 +
1404 + /**
1405 + * Get email checkout for case Guest
1406 + *
1407 + * @return string
1408 + */
1409 + public function get_checkout_email(): string {
1410 + return $this->get_data( 'checkout_email', '' );
1411 + }
1412 +
1413 + /**
1414 + * Short function to check order is completed.
1415 + *
1416 + * @return bool
1417 + */
1418 + public function is_completed(): bool {
1419 + return $this->get_status() === LP_ORDER_COMPLETED;
1420 + }
1421 +
1422 + /**
1423 + * Check Order is created manual.
1424 + *
1425 + * @return bool
1426 + * @since 4.1.3
1427 + * @author tungnx
1428 + * @version 1.0.0
1429 + */
1430 + public function is_manual(): bool {
1431 + return $this->get_created_via() === LP_ORDER_CREATED_VIA_MANUAL;
1432 + }
1433 +
1434 + /**
1435 + * Check can delete lp_user_items old
1436 + *
1437 + * @param LP_Course $course
1438 + *
1439 + * @return bool
1440 + * @throws Exception
1441 + * @author tungnx
1442 + * @since 4.1.4
1443 + * @version 1.0.0
1444 + */
1445 + public function check_can_delete_item_old( LP_Course $course ): bool {
1446 + $can_delete = false;
1447 +
1448 + $lp_user_items_db = LP_User_Items_DB::getInstance();
1449 +
1450 + /**
1451 + * For case user buy on frontend (not LP Order manual)
1452 + * And course enable repurchase and repurchase_type is keep
1453 + */
1454 + $allow_repurchase_type = '';
1455 +
1456 + $filter = new LP_User_Items_Filter();
1457 + $filter->user_id = get_current_user_id();
1458 + $filter->item_id = $course->get_id();
1459 + $user_course = $lp_user_items_db->get_last_user_course( $filter );
1460 +
1461 + if ( $user_course && isset( $user_course->user_item_id ) ) {
1462 + $latest_user_item_id = $user_course->user_item_id;
1463 +
1464 + /** Get allow_repurchase_type for reset or update. Add in: rest-api/v1/frontend/class-lp-courses-controller.php: purchase_course */
1465 + $allow_repurchase_type = learn_press_get_user_item_meta( $latest_user_item_id, '_lp_allow_repurchase_type' );
1466 + }
1467 +
1468 + /**
1469 + * Course is free
1470 + * Or not allow repurchase
1471 + * Or repurchase not type 'Keep'
1472 + * And else
1473 + * Will deleted lp_user_items old
1474 + */
1475 + if ( $course->is_free() || empty( $allow_repurchase_type ) || ! $course->allow_repurchase() || $allow_repurchase_type != 'keep' ) {
1476 + $can_delete = true;
1477 + }
1478 +
1479 + return apply_filters( 'learnpress/order/can_delete_old_item', $can_delete, $course );
1480 + }
1481 +
1482 + /**
1483 + * Get list status of LP Order
1484 + *
1485 + * @return array
1486 + */
1487 + public static function get_order_statuses(): array {
1488 + $order_statuses = array(
1489 + LP_ORDER_COMPLETED_DB => LP_ORDER_COMPLETED,
1490 + LP_ORDER_PROCESSING_DB => LP_ORDER_PROCESSING,
1491 + LP_ORDER_PENDING_DB => LP_ORDER_PENDING,
1492 + LP_ORDER_CANCELLED_DB => LP_ORDER_CANCELLED,
1493 + LP_ORDER_FAILED_DB => LP_ORDER_FAILED,
1494 + LP_ORDER_REFUNDED_DB => LP_ORDER_REFUNDED,
1495 + );
1496 +
1497 + return apply_filters( 'lp/order/statuses', $order_statuses );
1498 + }
1499 +
1500 + /**
1501 + * Query list orders.
1502 + *
1503 + * @param PostFilter $post_filter
1504 + * @param array $param [ 'author' => int, 'post_status' => string|array, 'refund_request_status' => string, 's' => string, 'm' => string, 'posts_per_page' => int, 'paged' => int, 'orderby' => string, 'order' => string ]
1505 + *
1506 + * @return void
1507 + * @throws Exception
1508 + * @since 4.3.2.8
1509 + * @version 1.0.2
1510 + */
1511 + public static function handle_params_query_list_orders( PostFilter &$post_filter, array $param = array() ) {
1512 + $post_db = PostDB::getInstance();
1513 + $user_of_order = absint( $param['author'] ?? 0 );
1514 + $status = $param['post_status'] ?? '';
1515 + $key = $param['s'] ?? '';
1516 + $month = $param['m'] ?? '';
1517 + $limit = $param['posts_per_page'] ?? 20;
1518 + $paged = $param['paged'] ?? 1;
1519 + $refund_request_status = sanitize_key( (string) ( $param['refund_request_status'] ?? '' ) );
1520 + $order_by = $param['orderby'] ?? 'menu_order';
1521 + if ( empty( $order_by ) ) {
1522 + $order_by = 'ID';
1523 + } else {
1524 + switch ( $order_by ) {
1525 + case 'date':
1526 + $order_by = 'post_date';
1527 + break;
1528 + default:
1529 + $allowed_key = array(
1530 + 'ID',
1531 + 'post_title',
1532 + 'post_author',
1533 + 'post_status',
1534 + 'order_total',
1535 + 'menu_order'
1536 + );
1537 + if ( ! in_array( $order_by, $allowed_key ) ) {
1538 + $order_by = 'ID';
1539 + }
1540 + break;
1541 + }
1542 + }
1543 +
1544 + $order = $param['order'] ?? 'DESC';
1545 + // End convert params
1546 +
1547 + if ( $order_by === 'order_total' ) {
1548 + $post_filter->join[] = "INNER JOIN {$post_db->tb_postmeta} pm2 ON p.ID = pm2.post_id AND pm2.meta_key = '_order_total'";
1549 + $post_filter->order_by = 'CAST(pm2.meta_value AS DECIMAL(10,2))';
1550 + } else {
1551 + $post_filter->order_by = $order_by;
1552 + }
1553 +
1554 + if ( ! empty( $key ) ) {
1555 + $pattern = '/^#\d+$/';
1556 + $is_order_id_sure = false;
1557 + if ( preg_match( $pattern, $key ) ) {
1558 + $is_order_id_sure = true;
1559 + $key = str_replace( '#', '', $key );
1560 + }
1561 +
1562 + $pattern2 = '#^0+.*\d+$#';
1563 + if ( preg_match( $pattern2, $key ) ) {
1564 + $key = (int) $key;
1565 + }
1566 +
1567 + $key = trim( $key );
1568 +
1569 + if ( $is_order_id_sure ) {
1570 + $post_filter->where[] = $post_db->wpdb->prepare( 'AND p.ID = %d', $key );
1571 + } else {
1572 + $post_filter->join[] = "INNER JOIN {$post_db->tb_lp_order_items} lpori ON p.ID = lpori.order_id";
1573 + $post_filter->where[] = $post_db->wpdb->prepare(
1574 + 'AND (p.ID = %d OR lpori.order_item_name like %s)',
1575 + $key,
1576 + '%' . $key . '%'
1577 + );
1578 + }
1579 + }
1580 +
1581 + if ( ! empty( $month ) ) {
1582 + $year = substr( $month, 0, 4 );
1583 + $post_filter->where[] = $post_db->wpdb->prepare(
1584 + 'AND YEAR(p.post_date) = %s',
1585 + $year
1586 + );
1587 + if ( strlen( $month ) > 5 ) {
1588 + $mon = substr( $month, 4, 2 );
1589 + $post_filter->where[] = $post_db->wpdb->prepare(
1590 + 'AND MONTH(p.post_date) = %s',
1591 + $mon
1592 + );
1593 + }
1594 + }
1595 +
1596 + $post_filter->order = $order;
1597 + $post_filter->limit = $limit;
1598 + $post_filter->page = $paged;
1599 +
1600 + if ( ! empty( $user_of_order ) ) {
1601 + $user_id = absint( $user_of_order );
1602 + $post_filter->join[] = "INNER JOIN {$post_db->tb_postmeta} pm1 ON p.ID = pm1.post_id AND pm1.meta_key = '_user_id'";
1603 + $post_filter->where[] = "AND ( pm1.meta_value like '%\"$user_id\"%' OR pm1.meta_value = $user_id )";
1604 + }
1605 +
1606 + if ( ! empty( $refund_request_status ) ) {
1607 + $post_filter->where[] = $post_db->wpdb->prepare(
1608 + "AND EXISTS (
1609 + SELECT 1 FROM {$post_db->tb_postmeta} refund_request_pm
1610 + WHERE refund_request_pm.post_id = p.ID
1611 + AND refund_request_pm.meta_key = %s
1612 + AND refund_request_pm.meta_value = %s
1613 + )",
1614 + LP_Order::META_KEY_REFUND_REQUEST,
1615 + $refund_request_status
1616 + );
1617 + }
1618 + if ( ! empty( $status ) && $status !== 'all' ) {
1619 + $post_filter->post_status = (array) $status;
1620 + } else {
1621 + $post_filter->where[] = $post_db->wpdb->prepare( 'AND p.post_status != %s', LP_ORDER_TRASH );
1622 + }
1623 + }
1624 +
1625 + /**
1626 + * Get transaction id from order meta
1627 + *
1628 + * @since 4.4.0
1629 + * @version 1.0.0
1630 + * @return string
1631 + */
1632 + public function get_transaction_id(): string {
1633 + return (string) $this->get_meta( self::META_KEY_TRANSACTION_ID );
1634 + }
1635 +
1636 + /**
1637 + * Get refund request status from order meta.
1638 + *
1639 + * @since 4.4.0
1640 + * @version 1.0.0
1641 + * @return string pending|approved|auto-approved|denied|''
1642 + */
1643 + public function get_refund_request(): string {
1644 + return (string) get_post_meta( $this->get_id(), self::META_KEY_REFUND_REQUEST, true );
1645 + }
1646 +
1647 + /**
1648 + * Check the order can be refunded.
1649 + *
1650 + * Validates multiple conditions required for refund eligibility:
1651 + * - Refund requests must be enabled in settings
1652 + * - Order must not already be refunded
1653 + * - Order must be in 'completed' status
1654 + * - Refund amount must not exceed order total (including previously refunded amount)
1655 + * - Payment gateway must have refund method implemented (override from abstract)
1656 + *
1657 + * @param float $amount The amount to refund..
1658 + *
1659 + * @return bool|WP_Error Returns true if refund is allowed, WP_Error with specific error code otherwise.
1660 + *
1661 + * Error codes:
1662 + * - 'refund_disabled': Refund feature is disabled in settings
1663 + * - 'order_refunded': Order already has refunded status
1664 + * - 'order_not_completed': Order is not in completed status
1665 + * - 'order_refund_amount_exceeds_total': Generic error (amount exceeds total or gateway unavailable)
1666 + * - 'order_refund_gateway_unavailable': Gateway does not support refund
1667 + *
1668 + * @since 4.4.0
1669 + * @version 1.0.0
1670 + */
1671 + public function can_refund( $amount = 0 ) {
1672 + try {
1673 + $error_code = '';
1674 + if ( 'yes' !== learn_press_get_refund_setting( 'enable_refund_requests', 'no' ) ) {
1675 + $error_code = 'refund_disabled';
1676 + throw new Exception( __( 'Refund requests are currently disabled.', 'learnpress' ) );
1677 + }
1678 +
1679 + if ( $this->has_status( LP_ORDER_REFUNDED ) ) {
1680 + $error_code = 'order_refunded';
1681 + throw new Exception( __( 'Order has refunded', 'learnpress' ) );
1682 + }
1683 +
1684 + if ( ! $this->has_status( LP_ORDER_COMPLETED ) ) {
1685 + $error_code = 'order_not_completed';
1686 + throw new Exception( __( 'Order is not completed', 'learnpress' ) );
1687 + }
1688 +
1689 + $refunded_amount = $this->get_meta( LP_Order::META_KEY_REFUNDED_AMOUNT );
1690 + if ( $refunded_amount > 0 ) {
1691 + $amount = $refunded_amount + $amount;
1692 + }
1693 +
1694 + if ( $amount > $this->get_total() ) {
1695 + $error_code = 'order_refund_amount_exceeds_total';
1696 + throw new Exception( __( 'Refund amount is greater than order total.', 'learnpress' ) );
1697 + }
1698 +
1699 + $payment_method = strtolower( $this->get_data( 'payment_method', '' ) );
1700 + $gateway = LP_Gateways::instance()->get_gateway( $payment_method );
1701 +
1702 + $has_refund_override = false;
1703 + if ( $gateway && method_exists( $gateway, 'refund' ) ) {
1704 + // Check gateway has refund method override
1705 + $reflection = new ReflectionMethod( $gateway, 'refund' );
1706 + $has_refund_override = $reflection->class === get_class( $gateway );
1707 + }
1708 +
1709 + if ( ! $gateway || ! $has_refund_override ) {
1710 + $error_code = 'order_refund_gateway_unavailable';
1711 + throw new Exception( __( 'Refund gateway is unavailable.', 'learnpress' ) );
1712 + }
1713 + } catch ( Throwable $e ) {
1714 + if ( empty( $error_code ) ) {
1715 + $error_code = 'order_can_not_refund';
1716 + }
1717 +
1718 + return new WP_Error( $error_code, $e->getMessage() );
1719 + }
1720 +
1721 + return true;
1722 + }
1723 +
1724 + /**
1725 + * Check user can send request refund
1726 + *
1727 + * Apply for cache 1 user 1 order, only user of this order can send request refund
1728 + *
1729 + * @since 4.4.0
1730 + * @version 1.0.0
1731 + * @return bool|WP_Error
1732 + */
1733 + public function can_send_request_refund( UserModel $userModel ) {
1734 + try {
1735 + $error_code = '';
1736 +
1737 + // Check user of this order
1738 + $order_user = (int) $this->get_user_id();
1739 + if ( $order_user !== $userModel->get_id() ) {
1740 + $error_code = 'request_refund_user_invalid';
1741 + throw new Exception(
1742 + __( 'You do not have permission to refund this order.', 'learnpress' )
1743 + );
1744 + }
1745 +
1746 + // Check refund requests are enabled
1747 + if ( 'yes' !== learn_press_get_refund_setting( 'enable_refund_requests', 'no' ) ) {
1748 + $error_code = 'refund_disabled';
1749 + throw new Exception( __( 'Refund requests are currently disabled.', 'learnpress' ) );
1750 + }
1751 +
1752 + // Check order total > 0
1753 + if ( $this->get_total() <= 0 ) {
1754 + $error_code = 'order_total_invalid';
1755 + throw new Exception( __( 'Order total is not valid.', 'learnpress' ) );
1756 + }
1757 +
1758 + // Check request refund status exists
1759 + $request_status = $this->get_refund_request();
1760 + if ( 'rejected' === $request_status ) {
1761 + if ( 'yes' !== learn_press_get_refund_setting( 'allow_resend_after_rejected', 'no' ) ) {
1762 + $error_code = 'request_refund_is_rejected';
1763 + throw new Exception( __( 'Request refund is rejected!.', 'learnpress' ) );
1764 + }
1765 + } elseif ( ! empty( $request_status ) ) {
1766 + $error_code = 'request_refund_sent';
1767 + throw new Exception(
1768 + sprintf(
1769 + __( 'Request refund has %s', 'learnpress' ),
1770 + $request_status
1771 + )
1772 + );
1773 + }
1774 +
1775 + // Check time limit for refund request
1776 + $refund_time_limit = learn_press_get_refund_setting( 'refund_time_limit', 30 );
1777 + if ( $refund_time_limit > 0 ) {
1778 + $order_time = absint( $this->get_order_date( 'timestamp' ) );
1779 + if ( $order_time > 0 ) {
1780 + $now = current_time( 'timestamp' );
1781 + $allowed_time_limit = strtotime( '+ ' . $refund_time_limit . ' day', $order_time );
1782 + if ( $allowed_time_limit < $now ) {
1783 + $error_code = 'request_refund_time_expired';
1784 + throw new Exception( __( 'Time for refund request expired', 'learnpress' ) );
1785 + }
1786 + }
1787 + }
1788 +
1789 + // Check gateway support refund
1790 + $payment_method = strtolower( $this->get_data( 'payment_method', '' ) );
1791 + $gateway = LP_Gateways::instance()->get_gateway( $payment_method );
1792 +
1793 + $gateway_has_refund_override = false;
1794 + if ( $gateway && method_exists( $gateway, 'refund' ) ) {
1795 + // Check gateway has refund method override
1796 + $reflection = new ReflectionMethod( $gateway, 'refund' );
1797 + $gateway_has_refund_override = $reflection->class === get_class( $gateway );
1798 + }
1799 +
1800 + if ( ! $gateway || ! $gateway_has_refund_override ) {
1801 + $error_code = 'order_refund_gateway_unavailable';
1802 + throw new Exception( __( 'Refund gateway is unavailable.', 'learnpress' ) );
1803 + }
1804 + } catch ( Throwable $e ) {
1805 + if ( empty( $error_code ) ) {
1806 + $error_code = 'user_can_not_send_request_refund';
1807 + }
1808 +
1809 + return new WP_Error( $error_code, $e->getMessage() );
1810 + }
1811 +
1812 + return true;
1813 + }
1814 +
1815 + /**
1816 + * Refund order with amount
1817 + *
1818 + * @return void
1819 + * @throws Exception
1820 + */
1821 + public function refund( $amount ) {
1822 + $can_refund = $this->can_refund( $amount );
1823 + if ( is_wp_error( $can_refund ) ) {
1824 + throw new Exception( $can_refund->get_error_message() );
1825 + }
1826 +
1827 + $payment_method = strtolower( $this->get_data( 'payment_method', '' ) );
1828 + $gateway = LP_Gateways::instance()->get_gateway( $payment_method );
1829 + /** @var LP_Gateway_Abstract|LP_Gateway_Paypal $gateway */
1830 + $gateway->refund( $this, $amount );
1831 +
1832 + $user_id = get_current_user_id();
1833 + update_post_meta( $this->get_id(), self::META_KEY_REFUNDED_AMOUNT, (float) $amount );
1834 + update_post_meta( $this->get_id(), self::META_KEY_REFUNDED_BY, $user_id );
1835 + update_post_meta( $this->get_id(), self::META_KEY_REFUNDED_AT, gmdate( LP_Datetime::$format, time() ) );
1836 + $this->update_status( LP_ORDER_REFUNDED );
1837 +
1838 + $this->add_note(
1839 + sprintf(
1840 + __( 'Order %1$s has been refunded %2$s.', 'learnpress' ),
1841 + $this->get_order_number(),
1842 + learn_press_format_price( $amount, learn_press_get_currency_symbol( $this->get_currency() ) )
1843 + )
1844 + );
1845 + }
1846 + }
1847 +}