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 +738 -298 4.1.74.4.8 View file →
@@ -3,14 +3,19 @@
3 3 * Class LP_Order
4 4 *
5 5 * @author ThimPress
6 6 * @package LearnPress/Classes
7 - * @version 4.0.0
7 + * @version 4.0.1
8 8 */
9 9
10 -/**
11 - * Prevent loading this file directly
12 - */
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 +
13 18 defined( 'ABSPATH' ) || exit();
14 19
15 20 if ( ! class_exists( 'LP_Order' ) ) {
16 21
@@ -59,8 +64,15 @@
59 64 '_user_ip' => '',
60 65 '_checkout_email' => '',
61 66 );
62 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 +
63 75 /**
64 76 * Store order status in transactions.
65 77 *
66 78 * @var array
@@ -101,16 +113,14 @@
101 113
102 114 /**
103 115 * Set order date.
104 116 *
105 - * @param int|string $date
117 + * @param int|string $date Date time string is time zone of WP setting
106 118 */
107 - public function set_order_date( $date ) {
108 - if ( is_numeric( $date ) ) {
109 - $date = gmdate( 'Y-m-d H:i:s', $date );
110 - }
111 - $this->_set_data_date( 'order_date', $date );
119 + public function set_order_date( $date ): LP_Order {
120 + $this->set_data_date( 'order_date', $date );
112 121
122 + return $this;
113 123 }
114 124
115 125 /**
116 126 * Get date of this order.
@@ -198,31 +208,8 @@
198 208 return apply_filters( 'learn-press/confirm-order-received-text', __( 'Thank you. Your order has been received.', 'learnpress' ), $this->get_id() );
199 209 }
200 210
201 211 /**
202 - * Magic function for getting object property dynamic.
203 - *
204 - * @param string $prop
205 - *
206 - * @return int|mixed|null
207 - * @deprecated
208 - */
209 - public function __get( $prop ) {
210 - if ( $prop == 'post' ) {
211 - // print_r( debug_backtrace() );
212 - // die( '$post is deprecated' );
213 - } elseif ( $prop == 'id' ) {
214 - return $this->get_id();
215 - }
216 - $value = null;
217 - if ( ! property_exists( $this, $prop ) ) {
218 - $value = get_post_meta( $this->get_id(), '_' . $prop, true );
219 - }
220 -
221 - return $value;
222 - }
223 -
224 - /**
225 212 * Checks to see if current order has status as passed.
226 213 *
227 214 * @param string|array $status String or an array of statuses
228 215 *
@@ -235,17 +222,8 @@
235 222 return apply_filters( 'learn-press/has-order-status', $has, $status, $this->get_id() );
236 223 }
237 224
238 225 /**
239 - * Check if order has a status as wp default.
240 - *
241 - * @return bool
242 - */
243 - public function has_invalid_status() {
244 - return ! $this->has_status( learn_press_get_order_statuses( false, true ) );
245 - }
246 -
247 - /**
248 226 * Updates order to new status if needed
249 227 *
250 228 * @param mixed $new_status
251 229 * @param bool $manual Is this a manual order status change?.
@@ -250,40 +228,23 @@
250 228 * @param mixed $new_status
251 229 * @param bool $manual Is this a manual order status change?.
252 230 *
253 231 * @return bool
254 - * @throws Exception
255 232 */
256 - public function update_status( $new_status = 'pending', $manual = false ) {
257 - $old_status = $this->get_status();
233 + public function update_status( $new_status = 'pending', $manual = false ): bool {
234 + $result = false;
258 235
259 - do_action( 'learn-press/before-update-status-lp-order', $new_status, $old_status, $this, $manual );
236 + try {
237 + $this->set_status( $new_status );
238 + $result = $this->save();
239 + } catch ( Throwable $e ) {
240 + error_log( $e->getMessage() );
241 + }
260 242
261 - $this->set_status( $new_status );
262 - $result = $this->save();
263 -
264 - do_action( 'learn-press/after-update-status-lp-order', $new_status, $old_status, $this, $manual );
265 -
266 243 return $result;
267 244 }
268 245
269 246 /**
270 - * Set payment method for this order.
271 - * If payment method is an instance of LP_Gateway_Abstract then
272 - * update it to database.
273 - *
274 - * @param LP_Gateway_Abstract|string $payment_method
275 - */
276 - public function set_payment_method( $payment_method ) {
277 - if ( $payment_method instanceof LP_Gateway_Abstract ) {
278 - update_post_meta( $this->get_id(), '_payment_method', $payment_method->get_id() );
279 - update_post_meta( $this->get_id(), '_payment_method_title', $payment_method->get_title() );
280 - }
281 -
282 - $this->payment_method = $payment_method;
283 - }
284 -
285 - /**
286 247 * Format order number id
287 248 *
288 249 * @return string
289 250 */
@@ -309,28 +270,94 @@
309 270 *
310 271 * @return mixed
311 272 */
312 273 public function get_status() {
313 - $status = $this->get_data( 'status', '' );
314 - $status = apply_filters( 'learn_press_order_status', $status, $this );
274 + return $this->get_data( 'status', '' );
275 + }
315 276
316 - apply_filters( 'learn-press/order/status', $status, $this->get_id() );
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 + }
317 316
317 + if ( ! is_string( $status ) ) {
318 + $status = '';
319 + }
320 +
318 321 return $status;
319 322 }
320 323
321 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 + /**
322 345 * Set order status.
346 + * $new_status shouldn't have prefix 'lp-'
323 347 *
324 348 * @param string $new_status
325 349 * @param string $note - Optional. Note for changing status.
350 + *
351 + * @sicne 3.0.0
352 + * @version 1.0.1
326 353 */
327 354 public function set_status( string $new_status = '', string $note = '' ) {
328 - $new_status = 'lp-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
329 - $valid_statuses = learn_press_get_order_statuses( false, true );
330 -
355 + // Ensure status not has prefix 'lp-'.
356 + $new_status = str_replace( 'lp-', '', $new_status );
357 + $valid_statuses = array_values( self::get_order_statuses() );
331 358 if ( ! in_array( $new_status, $valid_statuses ) && 'trash' !== $new_status ) {
332 - $new_status = 'pending';
359 + $new_status = LP_ORDER_PENDING;
333 360 }
334 361
335 362 $this->_set_data( 'status', $new_status );
336 363 }
@@ -335,24 +362,13 @@
335 362 $this->_set_data( 'status', $new_status );
336 363 }
337 364
338 365 public function get_order_status_html() {
339 - $statuses = learn_press_get_order_statuses();
340 - $order_status = $this->get_status();
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 );
341 370
342 - if ( ! empty( $statuses[ $order_status ] ) ) {
343 - $status = $statuses[ $order_status ];
344 - } elseif ( ! empty( $statuses[ 'lp-' . $order_status ] ) ) {
345 - $status = $statuses[ 'lp-' . $order_status ];
346 - } elseif ( $order_status == 'trash' ) {
347 - $status = __( 'Removed', 'learnpress' );
348 - } else {
349 - $status = ucfirst( $order_status );
350 - }
351 -
352 - $class = 'order-status order-status-' . sanitize_title( $status );
353 - $html = sprintf( '<span class="%s">%s</span>', apply_filters( 'learn_press_order_status_class', $class, $status, $this ), $status );
354 -
355 371 return apply_filters( 'learn_press_order_status_html', $html, $this );
356 372 }
357 373
358 374 /**
@@ -357,19 +373,15 @@
357 373
358 374 /**
359 375 * Mark order as complete
360 376 *
361 - * @param string - transaction ID provided payment gateway
377 + * @param string $transaction_id
362 378 *
363 379 * @return bool
364 - * @throws Exception
365 380 */
366 381 public function payment_complete( $transaction_id = '' ): bool {
367 382 do_action( 'learn-press/payment-pre-complete', $this->get_id() );
368 383
369 - //TODO: tungnx - check to change code below - use LP()->session->set()
370 - LP()->session->order_awaiting_payment = null;
371 -
372 384 $valid_order_statuses = apply_filters(
373 385 'learn-press/valid-order-statuses-for-payment-complete',
374 386 array(
375 387 'pending',
@@ -460,29 +472,46 @@
460 472
461 473 return apply_filters( 'learn-press/order/guest-customer-name', $customer_name );
462 474 }
463 475
464 - /*public function customer_exists() {
476 + /*
477 + public function customer_exists() {
465 478 return false !== get_userdata( $this->get_data( 'user_id' ) );
466 479 }*/
467 480
468 - public $order_items_loaded = false;
469 -
470 481 /**
471 - * Get items of the order
482 + * Get items of the order only to show.
472 483 *
473 484 * @return mixed
474 485 */
475 486 public function get_items() {
476 - if ( $this->order_items_loaded ) {
477 - return $this->order_items_loaded;
478 - }
487 + $items = $this->_curd->read_items( $this );
479 488
480 - $items = $this->_curd->read_items( $this );
489 + return apply_filters( 'learn-press/order-items', $items );
490 + }
481 491
482 - $this->order_items_loaded = $items;
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;
483 505
484 - return apply_filters( 'learn-press/order-items', $items );
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 );
485 514 }
486 515
487 516 /**
488 517 * Get items of the order by filter
@@ -512,15 +541,15 @@
512 541 * @return array|bool
513 542 * @editor tungnx
514 543 */
515 544 public function get_item_ids() {
516 - $items = $this->get_items();
545 + $items = $this->get_all_items();
517 546
518 547 if ( $items ) {
519 548 $course_ids = array();
520 549 foreach ( $items as $item ) {
521 - if ( isset( $item['course_id'] ) ) {
522 - $course_ids[] = $item['course_id'];
550 + if ( $item['item_type'] === LP_COURSE_CPT ) {
551 + $course_ids[] = (int) $item['item_id'];
523 552 }
524 553 }
525 554
526 555 return $course_ids;
@@ -527,9 +556,9 @@
527 556 /**
528 557 * Comment by tungnx. Because it will error if item didn't have key 'course_id'.
529 558 * Ex: case buy certificate, will not have that key
530 559 */
531 - //return @wp_list_pluck( $items, 'course_id' );
560 + // return @wp_list_pluck( $items, 'course_id' );
532 561 }
533 562
534 563 return false;
535 564 }
@@ -586,14 +615,14 @@
586 615 * @param array|int $item
587 616 *
588 617 * @return int
589 618 * @throws Exception
590 - * @editor tungnx
591 - * @modify 4.1.5
619 + * @since 1.0.0
620 + * @version 4.2.5
592 621 */
593 622 public function add_item( $item ): int {
594 623 global $wpdb;
595 - $lp_user_items_db = LP_User_Items_DB::getInstance();
624 + $order_item_id = 0;
596 625
597 626 try {
598 627 if ( is_numeric( $item ) ) {
599 628 $item = array(
@@ -601,9 +630,9 @@
601 630 'order_item_name' => get_the_title( $item ),
602 631 );
603 632 }
604 633
605 - $item_type = get_post_type( $item['item_id'] );
634 + $item_type = $item['item_type'] ?? get_post_type( $item['item_id'] );
606 635 if ( ! in_array( $item_type, learn_press_get_item_types_can_purchase() ) ) {
607 636 return false;
608 637 }
609 638
@@ -621,23 +650,12 @@
621 650 );
622 651
623 652 switch ( $item_type ) {
624 653 case LP_COURSE_CPT:
625 - $course = learn_press_get_course( $item['item_id'] );
626 - $item['subtotal'] = apply_filters( 'learnpress/order/item/subtotal', $course->get_price() * $item['quantity'], $course, $item );
627 - $item['total'] = apply_filters( 'learnpress/order/item/total', $course->get_price() * $item['quantity'], $course, $item );
628 - $item['order_item_name'] = apply_filters( 'learnpress/order/item/title', $course->get_title(), $course, $item );
629 -
630 - if ( $this->check_can_delete_item_old( $course ) ) {
631 - // Delete lp_user_items old
632 - $user_ids = $this->get_users();
633 - foreach ( $user_ids as $user_id ) {
634 - $lp_user_items_db->delete_user_items_old( $user_id, $course->get_id() );
635 - }
636 - // End
637 - }
638 -
639 - //learn_press_add_order_item_meta( $order_item_id, '_course_id', $item['item_id'] );
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 );
640 658 $item['meta']['_course_id'] = $item['item_id'];
641 659 break;
642 660 default:
643 661 $item = apply_filters( 'learnpress/order/add-item/item_type_' . $item_type, $item );
@@ -660,12 +678,15 @@
660 678 '%s',
661 679 )
662 680 );
663 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 );
664 685 // End insert new order item
665 686
666 687 // Add learnpress_order_itemmeta
667 - $item['meta']['_quantity'] = $item['quantity'];
688 + $item['meta']['_quantity'] = $item['quantity'] ?? 1;
668 689 $item['meta']['_subtotal'] = $item['subtotal'] ?? 0;
669 690 $item['meta']['_total'] = $item['total'] ?? 0;
670 691
671 692 if ( is_array( $item['meta'] ) ) {
@@ -696,9 +717,9 @@
696 717 *
697 718 * @return int|float
698 719 */
699 720 public function get_total() {
700 - return $this->get_data( 'total' );
721 + return $this->get_data( 'total', 0 );
701 722 }
702 723
703 724 /**
704 725 * @param float|int $subtotal
@@ -715,27 +736,21 @@
715 736 public function get_subtotal() {
716 737 return $this->get_data( 'subtotal' );
717 738 }
718 739
719 - public function cln() {
720 - return $this->_curd->cln( $this );
721 - }
722 -
723 - public function cln_items( $to ) {
724 - return $this->_curd->cln_items( $this->get_id(), $to );
725 - }
726 -
727 740 /**
728 741 * Remove an item from database and it's data.
729 742 *
730 - * @param int $item_id
743 + * @param $order_item_id
731 744 *
732 745 * @return bool
746 + * @throws Exception
733 747 */
734 - public function remove_item( $item_id ) {
748 + public function remove_item( $order_item_id ): bool {
735 749 global $wpdb;
736 750
737 - $item_id = absint( $item_id );
751 + $item_id = absint( $order_item_id );
752 + $order_item_id = absint( $order_item_id );
738 753
739 754 if ( ! $item_id ) {
740 755 return false;
741 756 }
@@ -746,11 +761,60 @@
746 761 * @since 3.0.0
747 762 */
748 763 do_action( 'learn-press/before-delete-order-item', $item_id, $this->get_id() );
749 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 +
750 810 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}learnpress_order_items WHERE order_item_id = %d", $item_id ) );
751 811 $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}learnpress_order_itemmeta WHERE learnpress_order_item_id = %d", $item_id ) );
752 812
813 + // Clear cache
814 + $key = "order/{$this->get_id()}/{$this->get_status()}/items";
815 + LP_Cache::cache_load_first( 'clear', $key );
816 +
753 817 /**
754 818 * @since 3.0.0
755 819 */
756 820 do_action( 'learn-press/deleted-order-item', $item_id, $this->get_id() );
@@ -769,13 +833,17 @@
769 833 */
770 834 public function add_items( $items ) {
771 835 settype( $items, 'array' );
772 836 $item_ids = array();
773 - foreach ( $items as $item ) {
774 - $item_id = $this->add_item( $item );
775 - if ( $item_id ) {
776 - $item_ids[] = $item_id;
837 + try {
838 + foreach ( $items as $item ) {
839 + $item_id = $this->add_item( $item );
840 + if ( $item_id ) {
841 + $item_ids[] = $item_id;
842 + }
777 843 }
844 + } catch ( Throwable $e ) {
845 + error_log( $e->getMessage() );
778 846 }
779 847
780 848 return $item_ids;
781 849 }
@@ -808,8 +876,9 @@
808 876
809 877 /**
810 878 * Get user id in array.
811 879 * user_id = 0 -> User type Guest
880 + *
812 881 * @return int[]
813 882 * @editor tungnx
814 883 * @modify 4.1.4
815 884 * @version 1.0.1
@@ -815,10 +884,18 @@
815 884 * @version 1.0.1
816 885 */
817 886 public function get_users(): array {
818 887 $users = $this->get_user_id();
819 - if ( $users !== -1 ) {
888 + if ( $users !== - 1 ) {
820 889 settype( $users, 'array' );
890 +
891 + $users = array_map(
892 + function ( $user ) {
893 + return absint( $user );
894 + },
895 + $users
896 + );
897 +
821 898 $users = array_unique( $users );
822 899 } else {
823 900 $users = array();
824 901 }
@@ -836,9 +913,9 @@
836 913 $found_selected = true;
837 914 } else {
838 915 $found_selected = false;
839 916 }
840 - echo sprintf( '<option value="%d" %s>%s</option>', esc_attr( $user->get_id() ), selected( $found_selected, true, false ), esc_html( $user->user_login ) );
917 + printf( '<option value="%d" %s>%s</option>', esc_attr( $user->get_id() ), selected( $found_selected, true, false ), esc_html( $user->user_login ) );
841 918 }
842 919 echo '</select>';
843 920 }
844 921
@@ -843,9 +920,8 @@
843 920 }
844 921
845 922
846 923 public function get_checkout_payment_url() {
847 -
848 924 }
849 925
850 926 public function get_formatted_order_subtotal() {
851 927 $currency_symbol = learn_press_get_currency_symbol( $this->get_currency() );
@@ -866,23 +942,26 @@
866 942 public function set_currency( $value ) {
867 943 $this->_set_data( 'currency', $value );
868 944 }
869 945
946 + /**
947 + * Get payment method title.
948 + *
949 + * @return array|mixed
950 + */
870 951 public function get_payment_method_title() {
871 - if ( $this->order_total == 0 ) {
872 - $title = '';
873 - } else {
874 - $title = $this->payment_method_title;
875 - }
876 -
877 - return $title;
952 + return esc_html( $this->get_data( 'payment_method_title', '' ) );
878 953 }
879 954
955 + /**
956 + * Get view order detail url.
957 + *
958 + * @return string
959 + */
880 960 public function get_view_order_url() {
881 961 global $wp_query;
882 962
883 - $view_order_url = learn_press_get_endpoint_url( 'view-order', $this->get_id(), learn_press_get_page_link( 'profile' ) );
884 - $user = learn_press_get_current_user();
963 + $userModel = UserModel::find( get_current_user_id(), true );
885 964 $view_order_endpoint = LP_Settings::instance()->get( 'profile_endpoints.order-details' );
886 965
887 966 if ( ! $view_order_endpoint ) {
888 967 $view_order_endpoint = 'order-details';
@@ -887,14 +966,21 @@
887 966 if ( ! $view_order_endpoint ) {
888 967 $view_order_endpoint = 'order-details';
889 968 }
890 969
970 + if ( ! $userModel ) {
971 + return '';
972 + }
973 +
974 + $user_slug = $userModel->user_nicename;
975 + $link_profile = learn_press_get_page_link( 'profile' );
976 +
891 977 $view_order_endpoint = urlencode( $view_order_endpoint );
892 978 if ( get_option( 'permalink_structure' ) ) {
893 - $view_order_url = learn_press_get_page_link( 'profile' ) . $user->get_data( 'user_login' ) . '/' . $view_order_endpoint . '/' . $this->get_id() . '/';
979 + $view_order_url = $link_profile . $user_slug . '/' . $view_order_endpoint . '/' . $this->get_id() . '/';
894 980 } else {
895 981 $args = array(
896 - 'user' => $user->get_data( 'user_login' ),
982 + 'user' => $user_slug,
897 983 );
898 984 $args['view'] = $view_order_endpoint;
899 985
900 986 if ( $view_order_endpoint ) {
@@ -901,9 +987,9 @@
901 987 $args['id'] = $this->get_id();
902 988 }
903 989 $view_order_url = add_query_arg(
904 990 $args,
905 - learn_press_get_page_link( 'profile' )
991 + $link_profile
906 992 );
907 993 }
908 994
909 995 return apply_filters( 'learn_press_view_order_url', $view_order_url, $this );
@@ -919,10 +1005,11 @@
919 1005 public function get_cancel_order_url( $force = false ) {
920 1006
921 1007 $url = false;
922 1008 if ( $this->has_status( 'pending' ) ) {
923 - $user = learn_press_get_current_user();
924 - $url = learn_press_user_profile_link( $user->get_id(), LP_Settings::instance()->get( 'profile_endpoints.profile-orders' ) );
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' ) );
925 1012 if ( ! $force ) {
926 1013 $url = esc_url_raw( add_query_arg( 'cancel-order', $this->get_id(), $url ) );
927 1014 } else {
928 1015 $url = esc_url_raw( add_query_arg( 'cancelled-order', $this->get_id(), $url ) );
@@ -934,13 +1021,53 @@
934 1021 return apply_filters( 'learn-press/order-cancel-url', $url, $this->get_id() );
935 1022 }
936 1023
937 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 + /**
938 1053 * Get profile order's actions.
939 1054 *
940 1055 * @return array|mixed
941 1056 */
942 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 +
943 1070 $actions = array(
944 1071 'view' => array(
945 1072 'url' => $this->get_view_order_url(),
946 1073 'text' => __( 'View', 'learnpress' ),
@@ -953,8 +1080,40 @@
953 1080 'url' => $this->get_cancel_order_url(),
954 1081 'text' => __( 'Cancel', 'learnpress' ),
955 1082 );
956 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 +
957 1116 $actions = apply_filters( 'learn-press/profile-order-actions', $actions, $this->get_id() );
958 1117
959 1118 return $actions;
960 1119 }
@@ -959,32 +1118,31 @@
959 1118 return $actions;
960 1119 }
961 1120
962 1121 public function add_note( $note = null ) {
1122 + $comment_author = '';
1123 + $comment_author_email = '';
963 1124 if ( is_user_logged_in() ) {
964 1125 $user = get_user_by( 'id', get_current_user_id() );
965 1126 $comment_author = $user->display_name;
966 1127 $comment_author_email = $user->user_email;
967 - $comment_post_ID = $this->get_id();
968 - $comment_author_url = '';
969 - $comment_content = $note;
970 - $comment_agent = 'LearnPress';
971 - $comment_type = 'lp_order_note';
972 - $comment_parent = 0;
973 - $comment_approved = 1;
1128 + }
974 1129
975 - $commentdata = apply_filters(
976 - 'learn_press_new_order_note_data',
977 - compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_agent', 'comment_type', 'comment_parent', 'comment_approved' ),
978 - $this->get_id()
979 - );
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;
980 1137
981 - $comment_id = wp_insert_comment( $commentdata );
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 + );
982 1143
983 - return $comment_id;
984 - }
985 -
986 - return false;
1144 + return wp_insert_comment( $commentdata );
987 1145 }
988 1146
989 1147 /**
990 1148 * Get user_name
@@ -989,8 +1147,9 @@
989 1147 /**
990 1148 * Get user_name
991 1149 *
992 1150 * @param int $user_id
1151 + *
993 1152 * @return string
994 1153 */
995 1154 public function get_user_name( int $user_id = 0 ): string {
996 1155 $user = learn_press_get_user( $user_id );
@@ -1014,38 +1173,8 @@
1014 1173 return is_array( $this->get_user_id() );
1015 1174 }
1016 1175
1017 1176 /**
1018 - * Print the list of all users has assigned to this order
1019 - * in case this order is for multi users
1020 - *
1021 - * @since 2.1.5
1022 - * @depecated 4.1.6.9
1023 - */
1024 - /*public function print_users() {
1025 - $user_ids = get_post_meta( $this->get_id(), '_user_id' );
1026 - if ( $user_ids ) {
1027 - global $wpdb;
1028 - if ( is_array( $user_ids[0] ) ) {
1029 - $user_ids = reset( $user_ids );
1030 - }
1031 - $format_ids = array_fill( 0, sizeof( $user_ids ), '%d' );
1032 - $sql = $wpdb->prepare( "SELECT user_login, user_email FROM {$wpdb->users} WHERE ID IN(" . join( ',', $format_ids ) . ')', $user_ids );
1033 - $users = $wpdb->get_results( $sql );
1034 - $size = sizeof( $users );
1035 -
1036 - foreach ( $users as $i => $user ) {
1037 - printf( '<strong>%s</strong> ( %s )', $user->user_login, $user->user_email );
1038 - if ( $i < $size - 1 ) {
1039 - echo ', ';
1040 - }
1041 - }
1042 - } else {
1043 - _e( 'No user assigned', 'learnpress' );
1044 - }
1045 - }*/
1046 -
1047 - /**
1048 1177 * Get email of user has bought this order.
1049 1178 * In case this order is for multi users return an array with multi email addresses.
1050 1179 *
1051 1180 * @return array
@@ -1057,9 +1186,9 @@
1057 1186 $user_ids = $this->get_users();
1058 1187 if ( ! empty( $user_ids ) ) {
1059 1188 foreach ( $user_ids as $user_id ) {
1060 1189 $user = learn_press_get_user( $user_id );
1061 - if ( $user->is_exists() ) {
1190 + if ( $user && $user->is_exists() ) {
1062 1191 $data[ $user_id ] = $user->get_data(
1063 1192 array(
1064 1193 'id',
1065 1194 'email',
@@ -1081,16 +1210,22 @@
1081 1210 /**
1082 1211 * Get email's user by user_id
1083 1212 *
1084 1213 * @param int $user_id
1214 + *
1085 1215 * @return string
1086 1216 * @editor tungnx
1087 1217 * @modify 4.1.3
1218 + * @version 1.0.3
1088 1219 */
1089 1220 public function get_user_email( int $user_id = 0 ): string {
1090 - $user = learn_press_get_user( $user_id );
1091 - if ( $user && ! $user instanceof LP_User_Guest ) {
1092 - $email = $user->get_email();
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();
1093 1228 } else {
1094 1229 $email = $this->get_checkout_email();
1095 1230 }
1096 1231
@@ -1096,16 +1231,8 @@
1096 1231
1097 1232 return $email;
1098 1233 }
1099 1234
1100 - public function get_child_orders( $force = false ) {
1101 - if ( $force ) {
1102 - wp_cache_delete( 'order-' . $this->get_id(), 'learn-press/child-orders' );
1103 - }
1104 -
1105 - return apply_filters( 'learn-press/child-orders', $this->_curd->get_child_orders( $this->get_id() ), $this->get_id() );
1106 - }
1107 -
1108 1235 /**
1109 1236 * Order title
1110 1237 *
1111 1238 * @param string $context
@@ -1142,9 +1269,9 @@
1142 1269 *
1143 1270 * @return array|int
1144 1271 */
1145 1272 public function get_user_id() {
1146 - return $this->get_data( 'user_id', -1 );
1273 + return $this->get_data( 'user_id', - 1 );
1147 1274 }
1148 1275
1149 1276 /**
1150 1277 * Get date modified of order.
@@ -1182,75 +1309,8 @@
1182 1309 return $this->get_data( 'created_via', '' );
1183 1310 }
1184 1311
1185 1312 /**
1186 - * Update order status if changed and trigger actions.
1187 - *
1188 - * @return bool
1189 - * @throws Exception
1190 - *
1191 - * @editor tungnx
1192 - * @modify 4.1.3 - comment - not use
1193 - */
1194 - // public function _save_status(): bool {
1195 - //
1196 - // // Nothing changed
1197 - // if ( ! $this->_status ) {
1198 - // return false;
1199 - // }
1200 - //
1201 - // $order_id = $this->get_id();
1202 - // $old_status = $this->_status['from'] ?? '';
1203 - // $new_status = $this->_status['to'] ?? '';
1204 - //
1205 - // // Only update if new status is difference with old status.
1206 - // if ( $new_status !== $old_status ) {
1207 - //
1208 - // if ( ! $this->get_user_id() ) {
1209 - // // $new_status = 'pending';
1210 - // }
1211 - //
1212 - // if ( doing_action( 'save_post' ) ) {
1213 - // // Update post's status using wpdb to preventing loop
1214 - // global $wpdb;
1215 - // $updated = $wpdb->update( $wpdb->posts, array( 'post_status' => 'lp-' . $new_status ), array( 'ID' => $order_id ), array( '%s' ) );
1216 - // } else {
1217 - // $updated = wp_update_post(
1218 - // array(
1219 - // 'post_status' => 'lp-' . $new_status,
1220 - // 'ID' => $order_id,
1221 - // )
1222 - // );
1223 - // }
1224 - //
1225 - // // Clear cache
1226 - // wp_cache_delete( $order_id, 'posts' );
1227 - //
1228 - // /**
1229 - // * Trigger actions after status was changed
1230 - // *
1231 - // * @deprecated
1232 - // */
1233 - // //do_action( 'learn_press_order_status_' . $new_status, $order_id );
1234 - // //do_action( 'learn_press_order_status_' . $old_status . '_to_' . $new_status, $order_id );
1235 - // //do_action( 'learn_press_order_status_changed', $order_id, $old_status, $new_status );
1236 - // // backward compatible
1237 - // do_action( 'learn_press_update_order_status', $new_status, $order_id );
1238 - //
1239 - // /**
1240 - // * @since 3.0.0
1241 - // */
1242 - // do_action( 'learn-press/order/status-' . $new_status, $order_id, $old_status );
1243 - // do_action( 'learn-press/order/status-' . $old_status . '-to-' . $new_status, $order_id );
1244 - // do_action( 'learn-press/order/status-changed', $order_id, $old_status, $new_status );
1245 - //
1246 - // return true;
1247 - // }
1248 - //
1249 - // return false;
1250 - // }
1251 -
1252 - /**
1253 1313 * Save order data.
1254 1314 *
1255 1315 * @return mixed
1256 1316 *
@@ -1257,28 +1317,44 @@
1257 1317 * @throws Exception
1258 1318 */
1259 1319 public function save() {
1260 1320 $old_status = '';
1321 + $is_created = ! $this->get_id();
1261 1322
1262 1323 if ( $this->get_id() ) {
1263 - $old_status = str_replace( 'lp-', '', get_post_status( $this->get_id() ) );
1264 - $return = $this->_curd->update( $this );
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 );
1265 1332 } else {
1266 - $return = $this->_curd->create( $this );
1333 + $return = $this->_curd->create( $this );
1334 + $this->_id = $return;
1267 1335 }
1268 1336
1269 - $new_status = str_replace( 'lp-', '', get_post_status( $this->get_id() ) );
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 + }
1270 1343
1271 1344 $order_id = $this->get_id();
1272 1345
1346 + if ( $is_created && $order_id ) {
1347 + do_action( 'learn-press/order/created', $order_id, $this );
1348 + }
1349 +
1273 1350 if ( $new_status !== $old_status ) {
1351 + do_action( 'learn-press/order/status-changed', $order_id, $old_status, $new_status );
1274 1352 do_action( 'learn-press/order/status-' . $new_status, $order_id, $old_status );
1275 1353 do_action( 'learn-press/order/status-' . $old_status . '-to-' . $new_status, $order_id );
1276 - do_action( 'learn-press/order/status-changed', $order_id, $old_status, $new_status );
1277 - //do_action( 'learn_press_update_order_status', $new_status, $order_id );
1278 1354 }
1279 1355
1280 - //$this->_save_status();
1356 + // $this->_save_status();
1281 1357
1282 1358 return $return;
1283 1359 }
1284 1360
@@ -1296,9 +1372,8 @@
1296 1372 return learn_press_get_order( $order );
1297 1373 }
1298 1374
1299 1375 /** Getter/Setter **/
1300 -
1301 1376 public function set_customer_message( $message ) {
1302 1377 $this->_set_data( 'customer_message', $message );
1303 1378 }
1304 1379
@@ -1340,9 +1415,9 @@
1340 1415 *
1341 1416 * @return bool
1342 1417 */
1343 1418 public function is_completed(): bool {
1344 - return $this->get_order_status() === 'completed';
1419 + return $this->get_status() === LP_ORDER_COMPLETED;
1345 1420 }
1346 1421
1347 1422 /**
1348 1423 * Check Order is created manual.
@@ -1352,9 +1427,9 @@
1352 1427 * @author tungnx
1353 1428 * @version 1.0.0
1354 1429 */
1355 1430 public function is_manual(): bool {
1356 - return $this->get_created_via() === 'manual';
1431 + return $this->get_created_via() === LP_ORDER_CREATED_VIA_MANUAL;
1357 1432 }
1358 1433
1359 1434 /**
1360 1435 * Check can delete lp_user_items old
@@ -1401,7 +1476,372 @@
1401 1476 $can_delete = true;
1402 1477 }
1403 1478
1404 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 + );
1405 1845 }
1406 1846 }
1407 1847 }