PluginProbe ʕ •ᴥ•ʔ
Tutor LMS – eLearning and online course solution / 4.0.0
Tutor LMS – eLearning and online course solution v4.0.0
4.0.0 3.9.15 3.9.14 3.9.13 3.9.12 3.9.11 trunk 1.0.0 1.0.0-alpha 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.1.1 1.2.0 1.2.1 1.2.11 1.2.12 1.2.13 1.2.20 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.9 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 1.7.6 1.7.7 1.7.8 1.7.9 1.8.0 1.8.1 1.8.10 1.8.2 1.8.3 1.8.4 1.8.5 1.8.6 1.8.7 1.8.8 1.8.9 1.9.0 1.9.1 1.9.10 1.9.11 1.9.12 1.9.13 1.9.14 1.9.15 1.9.16 1.9.2 1.9.3 1.9.4 1.9.5 1.9.6 1.9.7 1.9.8 1.9.9 2.0.0 2.0.1 2.0.10 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 2.1.0 2.1.1 2.1.10 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.1.8 2.1.9 2.2.0 2.2.1 2.2.2 2.2.3 2.2.4 2.3.0 2.4.0 2.5.0 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.1.0 3.2.0 3.2.1 3.2.2 3.2.3 3.3.0 3.3.1 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.7.0 3.7.1 3.7.2 3.7.3 3.7.4 3.8.0 3.8.1 3.8.2 3.8.3 3.9.0 3.9.1 3.9.10 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9
tutor / ecommerce / OrderController.php
tutor / ecommerce Last commit date
Cart 10 months ago PaymentGateways 4 weeks ago AdminMenu.php 1 day ago BillingController.php 1 day ago CartController.php 1 day ago CheckoutController.php 1 day ago CouponController.php 1 day ago Ecommerce.php 1 day ago EmailController.php 11 months ago HooksHandler.php 1 day ago OptionKeys.php 1 year ago OrderActivitiesController.php 1 year ago OrderController.php 1 day ago PaymentHandler.php 9 months ago Settings.php 1 day ago Tax.php 9 months ago currency.php 5 months ago
OrderController.php
1374 lines
1 <?php
2 /**
3 * Manage Order
4 *
5 * @package Tutor\Ecommerce
6 * @author Themeum <support@themeum.com>
7 * @link https://themeum.com
8 * @since 3.0.0
9 */
10
11 namespace Tutor\Ecommerce;
12
13 use TUTOR\Input;
14 use TUTOR\Earnings;
15 use Tutor\Models\OrderModel;
16 use TUTOR\Backend_Page_Trait;
17 use Tutor\Helpers\HttpHelper;
18 use Tutor\Models\CouponModel;
19 use Tutor\Models\CourseModel;
20 use Tutor\Helpers\QueryHelper;
21 use Tutor\Helpers\UrlHelper;
22 use Tutor\Traits\JsonResponse;
23 use Tutor\Helpers\ValidationHelper;
24 use Tutor\Models\OrderActivitiesModel;
25
26 if ( ! defined( 'ABSPATH' ) ) {
27 exit;
28 }
29 /**
30 * OrderController class
31 *
32 * @since 3.0.0
33 */
34 class OrderController {
35
36 /**
37 * Order page slug
38 *
39 * @since 3.0.0
40 *
41 * @var string
42 */
43 const PAGE_SLUG = 'tutor_orders';
44
45 /**
46 * Order model
47 *
48 * @since 3.0.0
49 *
50 * @var OrderModel
51 */
52 private $model;
53
54 /**
55 * Trait for utilities
56 *
57 * @var $page_title
58 */
59 use Backend_Page_Trait;
60
61 /**
62 * Trait for sending JSON response
63 */
64 use JsonResponse;
65
66 /**
67 * Constructor.
68 *
69 * Initializes the Orders class, sets the page title, and optionally registers
70 * hooks for handling AJAX requests related to order data, bulk actions, order status updates,
71 * and order deletions.
72 *
73 * @param bool $register_hooks Whether to register hooks for handling requests. Default is true.
74 *
75 * @since 3.0.0
76 *
77 * @return void
78 */
79 public function __construct( $register_hooks = true ) {
80 $this->model = new OrderModel();
81
82 if ( $register_hooks ) {
83 /**
84 * Handle AJAX request for getting order related data by order ID.
85 *
86 * @since 3.0.0
87 */
88 add_action( 'wp_ajax_tutor_order_details', array( $this, 'ajax_get_order_details' ) );
89
90 /**
91 * Handle AJAX request for marking an order as paid by order ID.
92 *
93 * @since 3.0.0
94 */
95 add_action( 'wp_ajax_tutor_order_paid', array( $this, 'order_mark_as_paid' ) );
96
97 /**
98 * Handle AJAX request for canceling an order status.
99 *
100 * @since 3.0.0
101 */
102 add_action( 'wp_ajax_tutor_order_cancel', array( $this, 'order_cancel' ) );
103
104 /**
105 * Handle AJAX request for marking an order's refund action.
106 *
107 * @since 3.0.0
108 */
109 add_action( 'wp_ajax_tutor_order_refund', array( $this, 'make_refund' ) );
110
111 /**
112 * Handle AJAX request for adding an order comment.
113 *
114 * @since 3.0.0
115 */
116 add_action( 'wp_ajax_tutor_order_comment', array( $this, 'add_comment' ) );
117
118 /**
119 * Handle AJAX request for add/update an order's discount action.
120 *
121 * @since 3.0.0
122 */
123 add_action( 'wp_ajax_tutor_order_discount', array( $this, 'add_discount' ) );
124
125 /**
126 * Handle bulk action
127 *
128 * @since 3.0.0
129 */
130 add_action( 'wp_ajax_tutor_order_bulk_action', array( $this, 'bulk_action_handler' ) );
131
132 add_filter( 'tutor_calculate_order_tax_amount', array( $this, 'filter_calculate_single_order_tax_amount' ), 10, 5 );
133
134 add_filter( 'tutor_get_orders_by_user_id', array( $this, 'filter_get_orders_by_user_id' ), 10, 3 );
135 add_filter( 'tutor_order_history_status_options', array( $this, 'filter_order_history_status_options' ), 10, 2 );
136 }
137 }
138
139 /**
140 * Page title fallback
141 *
142 * @since 3.5.0
143 *
144 * @param string $name Property name.
145 *
146 * @return string
147 */
148 public function __get( $name ) {
149 if ( 'page_title' === $name ) {
150 return esc_html__( 'Orders', 'tutor' );
151 }
152 }
153
154 /**
155 * Get order page url
156 *
157 * @since 3.0.0
158 *
159 * @param boolean $is_admin Whether to get admin or frontend url.
160 *
161 * @return string
162 */
163 public static function get_order_page_url( bool $is_admin = true ) {
164 if ( $is_admin ) {
165 return admin_url( 'admin.php?page=' . self::PAGE_SLUG );
166 } else {
167 return tutor_utils()->get_tutor_dashboard_url() . '/orders';
168 }
169 }
170
171 /**
172 * For Single Order
173 * Filter order tax calculation during create an order.
174 *
175 * @since 3.7.0
176 *
177 * @param int|float $tax_amount tax amount.
178 * @param int|float $total_price total price.
179 * @param int|float $tax_rate tax rate.
180 * @param string $order_type order type.
181 * @param array $items order items.
182 *
183 * @return int|float
184 */
185 public function filter_calculate_single_order_tax_amount( $tax_amount, $total_price, $tax_rate, $order_type, $items ) {
186 if ( OrderModel::TYPE_SINGLE_ORDER === $order_type ) {
187 $tax_exempt_price = 0;
188 $tax_amount = Tax::calculate_tax( $total_price, $tax_rate );
189
190 foreach ( $items as $item ) {
191 $is_tax_enabled = CourseModel::is_tax_enabled_for_single_purchase( $item['item_id'] ?? 0 );
192 if ( ! $is_tax_enabled ) {
193 $display_price = $this->model->get_order_item_display_price( (object) $item );
194 $tax_exempt_price += $display_price;
195 }
196 }
197
198 $tax_exempt_amount = Tax::calculate_tax( $tax_exempt_price, $tax_rate );
199 $tax_amount = $tax_amount - $tax_exempt_amount;
200 }
201
202 return $tax_amount;
203 }
204
205 /**
206 * Create order based on the arguments
207 *
208 * Note: This method assumes nonce & user cap has been validated.
209 *
210 * Note: This method will validate data so it could be
211 * used without validation.
212 *
213 * @since 3.0.0
214 *
215 * @since 3.9.2 `prepare_order_data` method added.
216 *
217 * @param int $user_id Typically student.
218 * @param array $items Key value pairs based on order_items table.
219 * @param string $payment_status Order payment status.
220 * @param string $order_type Type single_order/subscription.
221 * @param mixed $coupon_code Optional, if not provided automatic coupon.
222 * @param array $args Optional, Args to set data such as fees, tax, etc. Even to modify $order_data.
223 * @param bool $return_id return id.
224 *
225 * @throws \Exception Throw exception if data not valid or
226 * any other exception occur.
227 *
228 * @return mixed order id or order data.
229 */
230 public function create_order( int $user_id, array $items, string $payment_status, string $order_type, $coupon_code = null, array $args = array(), $return_id = true ) {
231
232 $order_data = $this->prepare_order_data( $user_id, $items, $payment_status, $order_type, $coupon_code, $args );
233
234 // Update data with arguments.
235 $order_data = apply_filters( 'tutor_before_order_create', array_merge( $order_data, $args ) );
236
237 try {
238 do_action( 'tutor_before_order_create', $order_data );
239 $order_id = $this->model->create_order( $order_data );
240 if ( $order_id ) {
241 $order_data['id'] = $order_id;
242 do_action( 'tutor_order_placed', $order_data );
243 return $return_id ? $order_id : $order_data;
244 }
245 } catch ( \Throwable $th ) {
246 throw new \Exception( $th->getMessage() );
247 }
248 }
249
250 /**
251 * Retrieve order data by order ID and respond with JSON.
252 *
253 * This function retrieves the order ID from the POST request, validates it,
254 * fetches the corresponding order data using the OrderModel class, and returns
255 * a JSON response with the order data or an error message.
256 *
257 * If the order ID is not provided, it responds with a "Bad Request" status.
258 * If the order is not found, it responds with a "Not Found" status.
259 * Otherwise, it responds with the order data and a success message.
260 *
261 * @since 3.0.0
262 *
263 * @return void
264 */
265 public function ajax_get_order_details() {
266 tutor_utils()->check_nonce();
267 tutor_utils()->check_current_user_capability();
268
269 $order_id = Input::post( 'order_id' );
270
271 if ( empty( $order_id ) ) {
272 $this->json_response(
273 __( 'Order ID is required', 'tutor' ),
274 null,
275 HttpHelper::STATUS_BAD_REQUEST
276 );
277 }
278
279 $order_data = $this->model->get_order_by_id( $order_id );
280
281 if ( ! $order_data ) {
282 $this->json_response(
283 __( 'Order not found', 'tutor' ),
284 null,
285 HttpHelper::STATUS_NOT_FOUND
286 );
287 }
288
289 $this->json_response(
290 __( 'Order retrieved successfully', 'tutor' ),
291 $order_data
292 );
293 }
294
295 /**
296 * Mark an order as paid.
297 *
298 * This function verifies a nonce for security, constructs a payload object with
299 * the order ID, note, and payment status, and updates the payment status of the order
300 * to 'paid'. It sends a JSON response indicating the success or failure of the operation.
301 *
302 * @since 3.0.0
303 *
304 * @return void
305 */
306 public function order_mark_as_paid() {
307 tutor_utils()->check_nonce();
308
309 if ( ! current_user_can( 'manage_options' ) ) {
310 $this->json_response( tutor_utils()->error_message( HttpHelper::STATUS_UNAUTHORIZED ), null, HttpHelper::STATUS_UNAUTHORIZED );
311 }
312
313 $params = array(
314 'order_id' => Input::post( 'order_id' ),
315 'note' => Input::post( 'note' ),
316 );
317
318 // Validate request.
319 $validation = $this->validate( $params );
320 if ( ! $validation->success ) {
321 $this->json_response(
322 tutor_utils()->error_message( HttpHelper::STATUS_BAD_REQUEST ),
323 $validation->errors,
324 HttpHelper::STATUS_BAD_REQUEST
325 );
326 }
327
328 $order_id = (int) $params['order_id'];
329
330 $updated = $this->model->mark_as_paid( $order_id, $params['note'] );
331
332 if ( ! $updated ) {
333 $this->json_response(
334 __( 'Failed to update order payment status', 'tutor' ),
335 null,
336 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
337 );
338 }
339
340 $this->json_response( __( 'Order payment status successfully updated', 'tutor' ) );
341 }
342
343 /**
344 * Cancels an order.
345 *
346 * This function cancels an order by updating its status to 'cancelled'. It performs nonce verification
347 * and checks the user's permissions before proceeding. It also validates the input parameters and
348 * triggers actions before and after the order cancellation.
349 *
350 * The function responds with an appropriate JSON message depending on the outcome of the cancellation process.
351 *
352 * @since 3.0.0
353 *
354 * @return void Responds with a JSON message indicating success or failure.
355 */
356 public function order_cancel() {
357 if ( ! tutor_utils()->is_nonce_verified() ) {
358 $this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
359 }
360
361 if ( ! current_user_can( 'manage_options' ) ) {
362 $this->json_response( tutor_utils()->error_message( HttpHelper::STATUS_UNAUTHORIZED ), null, HttpHelper::STATUS_UNAUTHORIZED );
363 }
364
365 $params = array(
366 'id' => Input::post( 'order_id' ),
367 'order_status' => $this->model::ORDER_CANCELLED,
368 );
369
370 // Validate request.
371 $validation = $this->validate( $params );
372 if ( ! $validation->success ) {
373 $this->json_response(
374 tutor_utils()->error_message( HttpHelper::STATUS_BAD_REQUEST ),
375 $validation->errors,
376 HttpHelper::STATUS_BAD_REQUEST
377 );
378 }
379
380 do_action( 'tutor_before_order_cancel', $params );
381
382 $response = $this->model->update_order( $params['id'], $params );
383 if ( ! $response ) {
384 $this->json_response(
385 __( 'Failed to cancel order status', 'tutor' ),
386 null,
387 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
388 );
389 }
390
391 do_action( 'tutor_order_payment_status_changed', $params['id'], '', $this->model::ORDER_CANCELLED );
392
393 $this->json_response( __( 'Order successfully canceled', 'tutor' ) );
394 }
395
396 /**
397 * Handle order refund process.
398 *
399 * This method processes the refund for an order. It verifies the nonce and user capabilities,
400 * triggers necessary actions before and after the refund process, validates input data, and
401 * interacts with the OrderActivitiesModel to record the refund metadata. If any validation
402 * fails or the refund process encounters an error, it returns an appropriate JSON response.
403 *
404 * @since 3.0.0
405 *
406 * @return void
407 */
408 public function make_refund() {
409 if ( ! tutor_utils()->is_nonce_verified() ) {
410 $this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
411 }
412
413 if ( ! current_user_can( 'manage_options' ) ) {
414 $this->json_response( tutor_utils()->error_message( HttpHelper::STATUS_UNAUTHORIZED ), null, HttpHelper::STATUS_UNAUTHORIZED );
415 }
416
417 $order_id = Input::post( 'order_id', 0, Input::TYPE_INT );
418 $amount = (float) Input::post( 'amount' );
419 $reason = Input::post( 'reason' );
420 $cancel_enrollment = Input::post( 'is_remove_enrolment', false, Input::TYPE_BOOL );
421
422 if ( $amount <= 0 ) {
423 $this->json_response( __( 'Invalid refund amount provided', 'tutor' ), null, HttpHelper::STATUS_BAD_REQUEST );
424 }
425
426 $meta_value = array(
427 'amount' => $amount,
428 'reason' => $reason,
429 'message' => __( 'Order refunded by ', 'tutor' ) . get_userdata( get_current_user_id() )->display_name,
430 );
431
432 $order_data = $this->model->get_order_by_id( $order_id );
433 $cancel_enrollment = apply_filters( 'tutor_cancel_enrollment_on_refund', $cancel_enrollment, $order_data );
434
435 if ( $amount > (float) $order_data->net_payment ) {
436 $this->json_response(
437 __( 'Refund amount exceeded.', 'tutor' ),
438 null,
439 HttpHelper::STATUS_BAD_REQUEST
440 );
441 }
442
443 $order_status = $order_data->order_status;
444 $payment_status = $order_data->payment_status;
445
446 try {
447 do_action( 'tutor_before_initiate_refund', $order_data );
448 } catch ( \Throwable $th ) {
449 $this->json_response(
450 $th->getMessage(),
451 '',
452 HttpHelper::STATUS_BAD_REQUEST
453 );
454 }
455
456 $meta_key = OrderActivitiesModel::META_KEY_REFUND;
457 if ( $amount < (float) $order_data->net_payment ) {
458 $meta_key = OrderActivitiesModel::META_KEY_PARTIALLY_REFUND;
459 }
460
461 if ( OrderActivitiesModel::META_KEY_PARTIALLY_REFUND === $meta_key ) {
462 $meta_value['message'] = __( 'Partially refunded by ', 'tutor' ) . get_userdata( get_current_user_id() )->display_name;
463 }
464
465 $params = array(
466 'order_id' => $order_id,
467 'meta_key' => $meta_key,
468 'meta_value' => wp_json_encode( $meta_value ),
469 );
470
471 // Validate request.
472 $validation = $this->validate( $params );
473 if ( ! $validation->success ) {
474 $this->json_response(
475 tutor_utils()->error_message( HttpHelper::STATUS_BAD_REQUEST ),
476 $validation->errors,
477 HttpHelper::STATUS_BAD_REQUEST
478 );
479 }
480
481 $payload = new \stdClass();
482 $payload->order_id = $params['order_id'];
483 $payload->meta_key = $params['meta_key'];
484 $payload->meta_value = $params['meta_value'];
485
486 $activity_model = new OrderActivitiesModel();
487 $response = $activity_model->add_order_meta( $payload );
488
489 if ( $response ) {
490 // Update net payment.
491 $refund_amount = $this->model->get_refund_amount( $order_id );
492
493 $net_payment = floatval( $order_data->total_price ) - floatval( $refund_amount );
494 if ( 'refund' === $meta_key ) {
495 $payment_status = $this->model::PAYMENT_REFUNDED;
496 } else {
497 $payment_status = $this->model::PAYMENT_PARTIALLY_REFUNDED;
498 }
499
500 if ( $cancel_enrollment ) {
501 $order_status = $this->model::ORDER_CANCELLED;
502 }
503
504 $update_data = array(
505 'net_payment' => $net_payment,
506 'refund_amount' => $refund_amount,
507 'payment_status' => $payment_status,
508 'order_status' => $order_status,
509 );
510
511 $this->model->update_order( $order_id, $update_data );
512
513 do_action( 'tutor_order_payment_status_changed', $order_data->id, $order_data->payment_status, $payment_status );
514
515 $order_data->payment_status = $update_data['payment_status'];
516 $order_data->order_status = $update_data['order_status'];
517 do_action( 'tutor_after_order_refund', $order_id, $amount, $reason );
518
519 $res_msg = __( 'Order refund successful', 'tutor' );
520
521 try {
522 $this->refund_from_payment_gateway( $order_id, $amount, $reason );
523 $this->json_response( $res_msg );
524 } catch ( \Throwable $th ) {
525 $res_msg = __( 'Order refunded successfully, but pending payment gateway issuance.', 'tutor' );
526 $this->json_response( $res_msg );
527 }
528 } else {
529 $this->json_response(
530 __( 'Failed to make refund', 'tutor' ),
531 null,
532 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
533 );
534 }
535 }
536
537 /**
538 * Handle adding a comment to an order.
539 *
540 * This method processes the addition of a comment to an order. It verifies the nonce and user capabilities,
541 * triggers necessary actions before and after the comment addition, validates input data, and
542 * interacts with the OrderActivitiesModel to record the comment metadata. If any validation
543 * fails or the comment addition process encounters an error, it returns an appropriate JSON response.
544 *
545 * @since 3.0.0
546 *
547 * @return void
548 */
549 public function add_comment() {
550 if ( ! tutor_utils()->is_nonce_verified() ) {
551 $this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
552 }
553
554 if ( ! current_user_can( 'manage_options' ) ) {
555 $this->json_response( tutor_utils()->error_message( HttpHelper::STATUS_UNAUTHORIZED ), null, HttpHelper::STATUS_UNAUTHORIZED );
556 }
557
558 $params = array(
559 'order_id' => Input::post( 'order_id' ),
560 'meta_key' => OrderActivitiesModel::META_KEY_COMMENT,
561 'meta_value' => Input::post( 'comment' ),
562 );
563
564 do_action( 'tutor_before_order_comment', $params );
565
566 // Validate request.
567 $validation = $this->validate( $params );
568 if ( ! $validation->success ) {
569 $this->json_response(
570 tutor_utils()->error_message( HttpHelper::STATUS_BAD_REQUEST ),
571 $validation->errors,
572 HttpHelper::STATUS_BAD_REQUEST
573 );
574 }
575
576 $payload = new \stdClass();
577 $payload->order_id = $params['order_id'];
578 $payload->meta_key = $params['meta_key'];
579 $payload->meta_value = $params['meta_value'];
580
581 $activity_model = new OrderActivitiesModel();
582 $response = $activity_model->add_order_meta( $payload );
583
584 do_action( 'tutor_after_order_comment', $params );
585
586 if ( ! $response ) {
587 $this->json_response(
588 __( 'Failed to make a comment', 'tutor' ),
589 null,
590 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
591 );
592 }
593
594 $this->json_response( __( 'Order comment successful added', 'tutor' ) );
595 }
596
597 /**
598 * Add a discount to an order.
599 *
600 * This function handles the request to add a discount to an order. It verifies the nonce,
601 * checks user permissions, validates the input, and then updates the order with the discount details.
602 *
603 * @since 3.0.0
604 *
605 * @return void This function outputs a JSON response and does not return a value.
606 */
607 public function add_discount() {
608 if ( ! tutor_utils()->is_nonce_verified() ) {
609 $this->json_response( tutor_utils()->error_message( 'nonce' ), null, HttpHelper::STATUS_BAD_REQUEST );
610 }
611
612 if ( ! current_user_can( 'manage_options' ) ) {
613 $this->json_response( tutor_utils()->error_message( HttpHelper::STATUS_UNAUTHORIZED ), null, HttpHelper::STATUS_UNAUTHORIZED );
614 }
615
616 $request = Input::sanitize_array( $_POST ); //phpcs:ignore --already sanitized.
617
618 // Validate request.
619 $validation = $this->validate( $request );
620 if ( ! $validation->success ) {
621 $this->json_response(
622 tutor_utils()->error_message( HttpHelper::STATUS_BAD_REQUEST ),
623 $validation->errors,
624 HttpHelper::STATUS_BAD_REQUEST
625 );
626 }
627
628 $request = (object) $request;
629
630 try {
631 $order = $this->model->get_order_by_id( $request->order_id );
632 $subtotal = $order->subtotal_price;
633
634 $discount_amount = $this->model->calculate_discount_amount( $request->discount_type, $request->discount_amount, $subtotal );
635
636 $deducted_amount = $discount_amount;
637 if ( ! empty( $order->coupon_code ) && $order->coupon_amount > 0 ) {
638 $deducted_amount += $order->coupon_amount;
639 }
640
641 $total_price = $subtotal - $deducted_amount;
642
643 $tax_rate = Tax::get_user_tax_rate( $order->user_id );
644 $tax_amount = Tax::calculate_tax( $total_price, $tax_rate );
645 if ( ! Tax::is_tax_included_in_price() ) {
646 $total_price += $tax_amount;
647 }
648
649 $order_data = array(
650 'discount_amount' => $request->discount_amount,
651 'discount_reason' => $request->discount_reason,
652 'discount_type' => $request->discount_type,
653 'subtotal_price' => $subtotal,
654 'tax_rate' => $tax_rate,
655 'tax_amount' => $tax_amount,
656 'net_payment' => $total_price,
657 'total_price' => $total_price,
658 );
659
660 $update = $this->model->update_order( $request->order_id, $order_data );
661 if ( ! $update ) {
662 $this->json_response(
663 __( 'Failed to add a discount', 'tutor' ),
664 null,
665 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
666 );
667 }
668
669 do_action( 'tutor_after_add_order_discount', $order, $discount_amount );
670
671 $this->json_response( __( 'Order discount successful added', 'tutor' ) );
672 } catch ( \Throwable $th ) {
673 $this->json_response(
674 __( 'Failed to add a discount', 'tutor' ),
675 $th->getMessage(),
676 HttpHelper::STATUS_INTERNAL_SERVER_ERROR
677 );
678 }
679 }
680
681 /**
682 * Prepare bulk actions that will show on dropdown options
683 *
684 * @return array
685 * @since 3.0.0
686 */
687 public function prepare_bulk_actions(): array {
688 $actions = array(
689 $this->bulk_action_default(),
690 );
691
692 $active_tab = Input::get( 'data', '' );
693
694 if ( $this->model::ORDER_TRASH !== $active_tab ) {
695 $actions[] = $this->bulk_action_mark_order_trash();
696 }
697
698 if ( ! empty( $active_tab ) ) {
699 switch ( $active_tab ) {
700 case $this->model::ORDER_INCOMPLETE:
701 $actions[] = $this->bulk_action_mark_order_paid();
702 break;
703 case $this->model::ORDER_COMPLETED:
704 $actions[] = $this->bulk_action_mark_order_unpaid();
705 break;
706 case $this->model::ORDER_TRASH:
707 $actions[] = $this->bulk_action_delete();
708 break;
709 default:
710 // code...
711 break;
712 }
713 }
714
715 return apply_filters( 'tutor_order_bulk_actions', $actions );
716 }
717
718 /**
719 * Available tabs that will visible on the right side of page navbar
720 *
721 * @since 3.0.0
722 * @since 4.0.0 param $context added.
723 *
724 * @param string $context context.
725 *
726 * @return array
727 */
728 public function tabs_key_value( $context = '' ): array {
729 $url = apply_filters( 'tutor_data_tab_base_url', get_pagenum_link() );
730
731 $date = Input::get( 'date', '' );
732 $payment_status = Input::get( 'payment-status', '' );
733 $search = Input::get( 'search', '' );
734
735 $where = array(
736 'order_type' => OrderModel::TYPE_SINGLE_ORDER,
737 );
738
739 if ( ! is_admin() ) {
740 $where['o.user_id'] = get_current_user_id();
741 }
742
743 if ( ! empty( $date ) ) {
744 $where['date(o.created_at_gmt)'] = tutor_get_formated_date( '', $date );
745 }
746
747 if ( 'dashboard' === $context ) {
748 /**
749 * Frontend dashboard will show all orders under `Billing > Order History` section.
750 * So we need to remove order_type from where clause.
751 *
752 * @since 4.0.0
753 */
754 if ( tutor_utils()->is_addon_enabled( 'subscription' ) ) {
755 unset( $where['order_type'] );
756 }
757
758 $start_date = Input::get( 'start_date', '' );
759 $end_date = Input::get( 'end_date', '' );
760 if ( ! empty( $start_date ) && ! empty( $end_date ) ) {
761 $where['date(o.created_at_gmt)'] = array( 'BETWEEN', array( tutor_get_formated_date( '', $start_date ), tutor_get_formated_date( '', $end_date ) ) );
762 }
763 }
764
765 if ( ! empty( $payment_status ) ) {
766 $where['o.payment_status'] = $payment_status;
767 }
768
769 $order_status = $this->model->get_order_status();
770
771 $tabs = array();
772
773 $tabs [] = array(
774 'key' => '',
775 'title' => __( 'All', 'tutor' ),
776 'value' => $this->model->get_order_count( $where, $search ),
777 'url' => UrlHelper::add_query_params( $url, array( 'data' => 'all' ) ),
778 );
779
780 foreach ( $order_status as $key => $value ) {
781 $where['order_status'] = $key;
782
783 $tabs[] = array(
784 'key' => $key,
785 'title' => $value,
786 'value' => $this->model->get_order_count( $where, $search ),
787 'url' => UrlHelper::add_query_params( $url, array( 'data' => $key ) ),
788 );
789 }
790
791 return apply_filters( 'tutor_order_tabs', $tabs );
792 }
793
794 /**
795 * Count orders by status & filters
796 * Count all | min | published | pending | draft
797 *
798 * @param string $status | required.
799 * @param string $order_id selected order id | optional.
800 * @param string $date selected date | optional.
801 * @param string $search_term search by user name or email | optional.
802 *
803 * @return int
804 *
805 * @since 3.0.0
806 */
807 protected static function count_order( string $status, $order_id = '', $date = '', $search_term = '' ): int {
808 $user_id = get_current_user_id();
809 $status = sanitize_text_field( $status );
810 $order_id = sanitize_text_field( $order_id );
811 $date = sanitize_text_field( $date );
812 $search_term = sanitize_text_field( $search_term );
813
814 $args = array(
815 'post_type' => tutor()->order_post_type,
816 );
817
818 if ( 'all' === $status || 'mine' === $status ) {
819 $args['post_status'] = array( 'publish', 'pending', 'draft', 'private', 'future' );
820 } else {
821 $args['post_status'] = array( $status );
822 }
823
824 // Author query.
825 if ( 'mine' === $status || ! current_user_can( 'administrator' ) ) {
826 $args['author'] = $user_id;
827 }
828
829 $date_filter = sanitize_text_field( $date );
830
831 $year = gmdate( 'Y', strtotime( $date_filter ) );
832 $month = gmdate( 'm', strtotime( $date_filter ) );
833 $day = gmdate( 'd', strtotime( $date_filter ) );
834
835 // Add date query.
836 if ( '' !== $date_filter ) {
837 $args['date_query'] = array(
838 array(
839 'year' => $year,
840 'month' => $month,
841 'day' => $day,
842 ),
843 );
844 }
845
846 if ( '' !== $order_id ) {
847 $args['p'] = $order_id;
848 }
849
850 // Search filter.
851 if ( '' !== $search_term ) {
852 $args['s'] = $search_term;
853 }
854
855 $the_query = new \WP_Query( $args );
856
857 return ! is_null( $the_query ) && isset( $the_query->found_posts ) ? $the_query->found_posts : $the_query;
858 }
859
860 /**
861 * Handle order bulk action
862 *
863 * @since 3.0.0
864 *
865 * @return void send wp_json response
866 */
867 public function bulk_action_handler() {
868
869 tutor_utils()->checking_nonce();
870
871 // Check if user is privileged.
872 if ( ! current_user_can( 'administrator' ) ) {
873 wp_send_json_error( tutor_utils()->error_message() );
874 }
875
876 $request = Input::sanitize_array( $_POST ); //phpcs:ignore -- already sanitized.
877 $bulk_action = $request['bulk-action'];
878
879 $bulk_ids = isset( $request['bulk-ids'] ) ? array_map( 'intval', explode( ',', $request['bulk-ids'] ) ) : array();
880
881 $allowed_bulk_actions = array(
882 $this->model::PAYMENT_PAID,
883 $this->model::PAYMENT_UNPAID,
884 $this->model::ORDER_TRASH,
885 'delete',
886 );
887
888 if ( ! in_array( $bulk_action, $allowed_bulk_actions, true ) ) {
889 wp_send_json_error( __( 'Please select appropriate action', 'tutor' ) );
890 }
891
892 if ( empty( $bulk_ids ) ) {
893 wp_send_json_error( __( 'No items selected for the bulk action.', 'tutor' ) );
894 }
895
896 do_action( 'tutor_before_order_bulk_action', $bulk_action, $bulk_ids );
897
898 $response = false;
899 if ( 'delete' === $bulk_action ) {
900 $response = $this->model->delete_order( $bulk_ids );
901 } else {
902 $data = null;
903
904 switch ( $bulk_action ) {
905 case $this->model::PAYMENT_PAID:
906 $data = array(
907 'payment_status' => $this->model::PAYMENT_PAID,
908 'order_status' => $this->model::ORDER_COMPLETED,
909 );
910 break;
911 case $this->model::PAYMENT_UNPAID:
912 $data = array(
913 'payment_status' => $this->model::PAYMENT_UNPAID,
914 'order_status' => $this->model::ORDER_INCOMPLETE,
915 );
916 break;
917 case $this->model::ORDER_TRASH:
918 $data = array(
919 'order_status' => $this->model::ORDER_TRASH,
920 );
921 break;
922 default:
923 // code...
924 break;
925 }
926
927 if ( ! empty( $data ) ) {
928 $response = $this->model->update_order( $bulk_ids, $data );
929 }
930 }
931
932 if ( $response ) {
933 if ( 'delete' !== $bulk_action ) {
934 foreach ( $bulk_ids as $id ) {
935 do_action( 'tutor_order_payment_status_changed', $id, '', $bulk_action );
936 }
937 }
938 wp_send_json_success( __( 'Order updated successfully.', 'tutor' ) );
939 } else {
940 wp_send_json_error( __( 'Failed to update order.', 'tutor' ) );
941 }
942 }
943
944 /**
945 * Execute bulk delete action
946 *
947 * @param string $bulk_ids ids that need to update.
948 * @return bool
949 * @since 3.0.0
950 */
951 public function bulk_delete_order( $bulk_ids ): bool {
952 $bulk_ids = explode( ',', sanitize_text_field( $bulk_ids ) );
953
954 $response = false;
955 try {
956 $response = QueryHelper::bulk_delete_by_ids( $this->model->get_table_name(), $bulk_ids );
957 } catch ( \Throwable $th ) {
958 error_log( $th->getMessage() . ' Line: ' . $th->getLine() . ' File: ' . $th->getFile() );
959 }
960
961 return $response;
962 }
963
964 /**
965 * Update order status
966 *
967 * @param string $status for updating order status.
968 * @param string $bulk_ids comma separated ids.
969 *
970 * @return bool
971 *
972 * @since 3.0.0
973 */
974 public static function update_order_status( string $status, $bulk_ids ): bool {
975 global $wpdb;
976 $post_table = $wpdb->posts;
977 $status = sanitize_text_field( $status );
978 $bulk_ids = sanitize_text_field( $bulk_ids );
979
980 $ids = array_map( 'intval', explode( ',', $bulk_ids ) );
981 $in_clause = QueryHelper::prepare_in_clause( $ids );
982
983 $update = $wpdb->query(
984 $wpdb->prepare(
985 "UPDATE {$post_table} SET post_status = %s WHERE ID IN ($in_clause)", //phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
986 $status
987 )
988 );
989
990 return true;
991 }
992
993 /**
994 * Get orders
995 *
996 * @since 3.0.0
997 *
998 * @param integer $limit List limit.
999 * @param integer $offset List offset.
1000 *
1001 * @return array
1002 */
1003 public function get_orders( $limit = 10, $offset = 0 ) {
1004
1005 $active_tab = Input::get( 'data', 'all' );
1006
1007 $date = Input::get( 'date', '' );
1008 $search_term = Input::get( 'search', '' );
1009 $payment_status = Input::get( 'payment-status', '' );
1010
1011 $where_clause = array(
1012 'order_type' => OrderModel::TYPE_SINGLE_ORDER,
1013 );
1014
1015 if ( $date ) {
1016 $where_clause['date(o.created_at_gmt)'] = tutor_get_formated_date( '', $date );
1017 }
1018
1019 if ( $payment_status ) {
1020 $where_clause['o.payment_status'] = $payment_status;
1021 }
1022
1023 if ( 'all' !== $active_tab ) {
1024 $where_clause['o.order_status'] = $active_tab;
1025 }
1026
1027 $list_order = Input::get( 'order', 'DESC' );
1028 $list_order_by = 'id';
1029
1030 return $this->model->get_orders( $where_clause, $search_term, $limit, $offset, $list_order_by, $list_order );
1031 }
1032
1033 /**
1034 * Filter discount data if monetization is Tutor
1035 *
1036 * @since 3.0.0
1037 *
1038 * @param int $user_id Current user id.
1039 * @param string $period Period for filter refund data.
1040 * @param string $start_date Filter start date.
1041 * @param string $end_date Filter end date.
1042 * @param int $course_id Course id.
1043 *
1044 * @return array
1045 */
1046 public function get_discount_data( $user_id = 0, $period = '', $start_date = '', $end_date = '', $course_id = 0 ) {
1047 // Sanitize params.
1048 $user_id = is_admin() ? 0 : $user_id;
1049 $period = Input::sanitize( $period );
1050 $start_date = Input::sanitize( $start_date );
1051 $end_date = Input::sanitize( $end_date );
1052 $course_id = (int) $course_id;
1053
1054 return $this->model->get_discounts_by_user( $user_id, $period, $start_date, $end_date, $course_id );
1055 }
1056
1057 /**
1058 * Filter refund data if monetization is Tutor
1059 *
1060 * @since 3.0.0
1061 *
1062 * @param int $user_id Current user id.
1063 * @param string $period Period for filter refund data.
1064 * @param string $start_date Filter start date.
1065 * @param string $end_date Filter end date.
1066 * @param int $course_id Course id.
1067 *
1068 * @return array
1069 */
1070 public function get_refund_data( $user_id = 0, $period = '', $start_date = '', $end_date = '', $course_id = 0 ) {
1071 // Sanitize params.
1072 $user_id = is_admin() ? 0 : $user_id;
1073 $period = Input::sanitize( $period );
1074 $start_date = Input::sanitize( $start_date );
1075 $end_date = Input::sanitize( $end_date );
1076 $course_id = (int) $course_id;
1077
1078 return $this->model->get_refunds_by_user( $user_id, $period, $start_date, $end_date, $course_id );
1079 }
1080
1081 /**
1082 * Validate input data based on predefined rules.
1083 *
1084 * This protected method validates the provided data array against a set of
1085 * predefined validation rules. The rules specify that 'order_id' is required
1086 * and must be numeric. The method will skip validation rules for fields that
1087 * are not present in the data array.
1088 *
1089 * @since 3.0.0
1090 *
1091 * @param array $data The data array to validate.
1092 *
1093 * @return object The validation result. It returns validation object.
1094 */
1095 protected function validate( array $data ) {
1096
1097 $validation_rules = array(
1098 'order_id' => 'required|numeric',
1099 'meta_key' => 'required',
1100 'meta_value' => 'required',
1101 'discount_type' => 'required',
1102 'discount_amount' => 'required',
1103 );
1104
1105 // Skip validation rules for not available fields in data.
1106 foreach ( $validation_rules as $key => $value ) {
1107 if ( ! array_key_exists( $key, $data ) ) {
1108 unset( $validation_rules[ $key ] );
1109 }
1110 }
1111
1112 return ValidationHelper::validate( $validation_rules, $data );
1113 }
1114
1115 /**
1116 * Process refund from payment gateway
1117 *
1118 * @since 3.1.0
1119 *
1120 * @param int $order_id Order id.
1121 * @param string $amount Refund amount.
1122 * @param string $reason Refund reason.
1123 *
1124 * @throws \Throwable If an error occurs during the refund process.
1125 *
1126 * @return void
1127 */
1128 public function refund_from_payment_gateway( $order_id, $amount, $reason ) {
1129 $order = $this->model->get_order_by_id( $order_id );
1130 if ( $order && ! $this->model->is_manual_payment( $order->payment_method ) ) {
1131 $refund_data = $this->prepare_refund_data( $order, $amount, $reason );
1132 try {
1133 $payment_gateway_ref = Ecommerce::payment_gateways_with_ref( $order->payment_method );
1134 if ( $payment_gateway_ref ) {
1135 $gateway_obj = Ecommerce::get_payment_gateway_object( $payment_gateway_ref['gateway_class'] );
1136 $gateway_obj->make_refund( $refund_data );
1137 }
1138 } catch ( \Throwable $th ) {
1139 throw $th;
1140 }
1141 }
1142 }
1143
1144 /**
1145 * Prepare refund data
1146 *
1147 * @since 3.1.0
1148 *
1149 * @param object $order Order object.
1150 * @param string $amount Raw amount.
1151 * @param string $reason Refund reason.
1152 *
1153 * @return object
1154 */
1155 public function prepare_refund_data( $order, $amount, $reason ) {
1156 $currency = tutor_get_currencies_info_by_code( tutor_utils()->get_option( OptionKeys::CURRENCY_CODE ) );
1157
1158 $refund_data = array(
1159 'type' => 'refund',
1160 'amount' => $amount,
1161 'payment_payload' => $order->payment_payloads, // JSON string representing the payment payload.
1162 'order_id' => $order->id,
1163 'reason' => $reason,
1164 'refund_type' => $order->net_payment == $amount ? 'full' : 'partial',
1165 'currency' => (object) array(
1166 'code' => $currency['code'],
1167 'symbol' => $currency['symbol'],
1168 'name' => $currency['name'],
1169 'locale' => $currency['locale'],
1170 'numeric_code' => $currency['numeric_code'],
1171 ),
1172 );
1173
1174 return (object) $refund_data;
1175 }
1176
1177 /**
1178 * Prepare sanitized and calculated order data for database insertion or update.
1179 *
1180 * @since 3.9.2
1181 *
1182 * @param int $user_id The ID of the user placing or updating the order.
1183 * @param array $items The list of items included in the order.
1184 * @param string $payment_status The current payment status (e.g., 'paid', 'unpaid').
1185 * @param string $order_type The type of order (e.g., single, subscription, renewal).
1186 * @param string|null $coupon_code Optional coupon code applied to the order.
1187 * @param array $args Optional additional arguments, such as discounts or custom data.
1188 *
1189 * @return array The fully prepared order data array, ready to be stored or updated.
1190 *
1191 * @throws \Exception If the payment status is invalid or required data is missing.
1192 */
1193 private function prepare_order_data( int $user_id, array $items, string $payment_status, string $order_type, $coupon_code = null, array $args = array() ): array {
1194 $items = Input::sanitize_array( $items );
1195 $payment_status = Input::sanitize( $payment_status );
1196 $coupon_code = Input::sanitize( $coupon_code );
1197
1198 $allowed_item_fields = $this->model->get_order_items_fillable_fields();
1199 unset( $allowed_item_fields['order_id'] );
1200
1201 // Validate order items.
1202 if ( ! isset( $items[0] ) ) {
1203 $items = array( $items );
1204 }
1205
1206 // Validate payment status.
1207 if ( ! in_array( $payment_status, array_keys( $this->model->get_payment_status() ) ) ) {
1208 throw new \Exception( __( 'Invalid payment status', 'tutor' ) );
1209 }
1210
1211 $subtotal_price = 0;
1212 $total_price = 0;
1213
1214 if ( $this->model::TYPE_SINGLE_ORDER === $order_type ) {
1215 $item_price = $this->model::calculate_order_price( $items );
1216 $subtotal_price = $item_price->subtotal;
1217 $total_price = $item_price->total;
1218 } else {
1219 // For subscription and renewal order.
1220 $prices = apply_filters( 'tutor_create_order_prices_for_subscription', null, $items, $order_type, $user_id );
1221 if ( $prices ) {
1222 $subtotal_price = $prices->subtotal_price;
1223 $total_price = $prices->total_price;
1224 }
1225 }
1226
1227 $order_data = array(
1228 'items' => $items,
1229 'payment_status' => $payment_status,
1230 'order_type' => $order_type,
1231 'coupon_code' => $coupon_code,
1232 'coupon_amount' => isset( $args['coupon_amount'] ) ? $args['coupon_amount'] : null,
1233 'subtotal_price' => $subtotal_price,
1234 'pre_tax_price' => $total_price,
1235 'total_price' => $total_price,
1236 'net_payment' => $total_price,
1237 'user_id' => $user_id,
1238 'order_status' => $this->model::PAYMENT_PAID === $payment_status ? $this->model::ORDER_COMPLETED : $this->model::ORDER_INCOMPLETE,
1239 'created_at_gmt' => current_time( 'mysql', true ),
1240 'created_by' => get_current_user_id(),
1241 'updated_at_gmt' => current_time( 'mysql', true ),
1242 'updated_by' => get_current_user_id(),
1243 );
1244
1245 if ( isset( $args['discount_amount'] ) && $args['discount_amount'] > 0 ) {
1246 $order_data['discount_type'] = 'flat';
1247 $order_data['discount_amount'] = floatval( $args['discount_amount'] );
1248 $order_data['discount_reason'] = __( 'Sale discount', 'tutor' );
1249 }
1250
1251 $calculate_tax = apply_filters( 'tutor_calculate_order_tax', Tax::should_calculate_tax(), $args );
1252
1253 if ( $calculate_tax ) {
1254 /**
1255 * Tax calculation for order.
1256 */
1257 $tax_rate = Tax::get_user_tax_rate( $user_id );
1258 if ( $tax_rate ) {
1259 $tax_amount = apply_filters( 'tutor_calculate_order_tax_amount', 0, $total_price, $tax_rate, $order_type, $items );
1260
1261 $order_data['tax_type'] = Tax::get_tax_type();
1262 $order_data['tax_rate'] = $tax_rate;
1263 $order_data['tax_amount'] = $tax_amount;
1264
1265 if ( ! Tax::is_tax_included_in_price() ) {
1266 $total_price += $tax_amount;
1267 $order_data['total_price'] = $total_price;
1268 $order_data['net_payment'] = $total_price;
1269 } else {
1270 $order_data['pre_tax_price'] = $total_price - $tax_amount;
1271 }
1272 }
1273 }
1274
1275 return $order_data;
1276 }
1277
1278 /**
1279 * Update an existing order with validated and recalculated data.
1280 *
1281 * @since 3.9.2
1282 *
1283 * @param int $order_id The unique ID of the order to update.
1284 * @param int $user_id The ID of the user associated with the order.
1285 * @param array $items The list of items to update for the order.
1286 * @param string $payment_status The payment status (e.g., 'paid', 'unpaid').
1287 * @param string $order_type The order type (e.g., single, subscription, renewal).
1288 * @param string|null $coupon_code Optional coupon code applied to the order.
1289 * @param array $args Optional additional arguments (discounts, tax data, etc.).
1290 * @param bool $return_data Whether to return the updated order data on success.
1291 *
1292 * @return array|bool The updated order data if $return_data is true and the update succeeds,
1293 * or false otherwise.
1294 */
1295 public function update_order( int $order_id, int $user_id, array $items, string $payment_status, string $order_type, $coupon_code = null, array $args = array(), $return_data = false ) {
1296
1297 $update_data = $this->prepare_order_data( $user_id, $items, $payment_status, $order_type, $coupon_code, $args );
1298 $order_items = $update_data['items'];
1299
1300 unset( $update_data['items'], $update_data['created_at_gmt'], $update_data['created_by'] );
1301
1302 if ( $this->model->update_order( $order_id, $update_data, $order_items ) ) {
1303 $update_data['id'] = $order_id;
1304 $update_data['items'] = $order_items;
1305 do_action( 'tutor_order_updated', $update_data );
1306 return $return_data ? $update_data : true;
1307 }
1308
1309 return false;
1310 }
1311
1312 /**
1313 * Filter get orders by user id.
1314 *
1315 * @since 4.0.0
1316 *
1317 * @param object $data Order data.
1318 * @param int $user_id User ID.
1319 * @param array $args Array of arguments.
1320 *
1321 * @return object {results: array, total_count: int}
1322 */
1323 public function filter_get_orders_by_user_id( $data, $user_id, $args ) {
1324 if ( ! $user_id ) {
1325 return $data;
1326 }
1327
1328 $other_params = array();
1329 if ( ! tutor_utils()->is_addon_enabled( 'subscription' ) ) {
1330 $other_params['order_type'] = OrderModel::TYPE_SINGLE_ORDER;
1331 }
1332
1333 $order_status = Input::sanitize( $args['status'] ?? 'all' );
1334 $period = isset( $args['period'] ) ? Input::sanitize( $args['period'] ) : null;
1335 $start_date = isset( $args['start_date'] ) ? Input::sanitize( $args['start_date'] ) : null;
1336 $end_date = isset( $args['end_date'] ) ? Input::sanitize( $args['end_date'] ) : null;
1337 $offset = (int) $args['offset'] ?? 0;
1338 $limit = (int) $args['limit'] ?? 0;
1339 $order = QueryHelper::get_valid_sort_order( $args['order'] ?? 'DESC' );
1340
1341 $data = $this->model->get_user_orders( $period, $start_date, $end_date, $order_status, $user_id, $limit, $offset, $order, $other_params );
1342
1343 return (object) $data;
1344 }
1345
1346 /**
1347 * Filter order history status options.
1348 *
1349 * @since 4.0.0
1350 *
1351 * @param array $options Order history status options.
1352 * @param string $selected Selected status.
1353 *
1354 * @return array<array{label: string, value: string, count: int, url: string, active: bool}>
1355 */
1356 public function filter_order_history_status_options( $options, $selected ) {
1357 $options = $this->tabs_key_value( 'dashboard' );
1358 $options = array_map(
1359 function( $option ) use ( $selected ) {
1360 $key = $option['key'] ?? '';
1361 return array(
1362 'label' => $option['title'] ?? '',
1363 'value' => $key,
1364 'count' => (int) $option['value'] ?? 0,
1365 'url' => $option['url'] ?? '',
1366 'active' => $key === $selected || ( empty( $key ) && 'all' === $selected ),
1367 );
1368 },
1369 $options
1370 );
1371 return $options;
1372 }
1373 }
1374