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