PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.2
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.2
4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 4.2.0 All 139 releases
learnpress / inc / order / lp-order-functions.php

lp-order-functions.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.2, at inc/order/lp-order-functions.php

666 lines 19.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Defines functions related to order
4 *
5 * @author ThimPress
6 * @package LearnPress/Functions
7 * @version 1.0
8 */
9 use LearnPress\Models\UserItems\UserCourseModel;
10 use LearnPress\Models\UserModel;
11
12 defined( 'ABSPATH' ) || exit;
13
14 /**
15 * Generate unique key for an order.
16 *
17 * @return mixed
18 */
19 function learn_press_generate_order_key() {
20 return apply_filters( 'learn-press/order-key', strtoupper( uniqid( 'ORDER' ) ) );
21 }
22
23 /**
24 * Update Order status
25 *
26 * @param int
27 * @param string
28 *
29 * @return bool
30 */
31 function learn_press_update_order_status( $order_id, $status = '' ) {
32 $order = new LP_Order( $order_id );
33 if ( $order ) {
34 return $order->update_status( $status );
35 }
36
37 return false;
38 }
39
40 /**
41 * Add order item meta data.
42 *
43 * @param int $item_id
44 * @param string $meta_key
45 * @param mixed $meta_value
46 * @param string $prev_value
47 *
48 * @return false|int
49 */
50 function learn_press_add_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) {
51 return add_metadata( 'learnpress_order_item', $item_id, $meta_key, $meta_value, $prev_value );
52 }
53
54 /**
55 * Update order item meta data.
56 *
57 * @param int $item_id
58 * @param string $meta_key
59 * @param mixed $meta_value
60 * @param string $prev_value
61 *
62 * @return bool|int
63 */
64 function learn_press_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) {
65 return update_metadata( 'learnpress_order_item', $item_id, $meta_key, $meta_value, $prev_value );
66 }
67
68 /**
69 * Delete order item meta data.
70 *
71 * @param int $item_id
72 * @param string $meta_key
73 * @param mixed $meta_value
74 * @param bool $delete_all
75 *
76 * @return bool
77 */
78 function learn_press_delete_order_item_meta( $item_id, $meta_key, $meta_value, $delete_all = false ) {
79 return delete_metadata( 'learnpress_order_item', $item_id, $meta_key, $meta_value, $delete_all );
80 }
81
82 /**
83 * Get order item meta data.
84 *
85 * @param int $item_id
86 * @param string $meta_key
87 * @param bool $single
88 *
89 * @return mixed
90 */
91 function learn_press_get_order_item_meta( $item_id, $meta_key, $single = true ) {
92 return get_metadata( 'learnpress_order_item', $item_id, $meta_key, $single );
93 }
94
95 /**
96 * Get order
97 *
98 * @param mixed $the_order
99 *
100 * @return LP_Order|bool object instance
101 */
102 function learn_press_get_order( $the_order = false ) {
103 global $post;
104 $the_id = 0;
105 if ( false === $the_order && is_a( $post, 'WP_Post' ) && LP_ORDER_CPT === get_post_type( $post ) ) {
106 $the_id = $post->ID;
107 } elseif ( is_numeric( $the_order ) ) {
108 $the_id = $the_order;
109 } elseif ( $the_order instanceof LP_Order ) {
110 $the_id = $the_order->get_id();
111 } elseif ( ! empty( $the_order->ID ) ) {
112 $the_id = $the_order->ID;
113 }
114
115 if ( LP_ORDER_CPT != get_post_type( $the_id ) ) {
116 return false;
117 }
118
119 return new LP_Order( $the_id );
120 }
121
122 /**
123 * Count orders by it's status
124 *
125 * @param array $args
126 * @Todo tungnx review to rewrite query
127 * @return array
128 */
129 function learn_press_count_orders( $args = array() ) {
130 if ( is_string( $args ) ) {
131 $args = array( 'status' => $args );
132 } else {
133 $args = wp_parse_args(
134 $args,
135 array(
136 'status' => '',
137 )
138 );
139 }
140 global $wpdb;
141 $statuses = $args['status'];
142
143 if ( ! $statuses ) {
144 $statuses = array_keys( LP_Order::get_order_statuses() );
145 }
146
147 settype( $statuses, 'array' );
148 $size_of_status = sizeof( $statuses );
149
150 foreach ( $statuses as $k => $status ) {
151 $statuses[ $k ] = ! preg_match( '~^lp-~', $status ) ? 'lp-' . $status : $status;
152 }
153
154 $format = array_fill( 0, $size_of_status, '%s' );
155 $counts = array_fill_keys( $statuses, 0 );
156 $statuses[] = LP_ORDER_CPT;
157 $query = $wpdb->prepare(
158 "
159 SELECT COUNT(ID) AS count, post_status AS status
160 FROM {$wpdb->posts} o
161 WHERE post_status IN(" . join( ',', $format ) . ')
162 AND post_type = %s
163 GROUP BY o.post_status
164 ',
165 $statuses
166 );
167
168 $results = $wpdb->get_results( $query );
169 if ( $results ) {
170 foreach ( $results as $result ) {
171 if ( array_key_exists( $result->status, $counts ) ) {
172 $counts[ $result->status ] = absint( $result->count );
173 }
174 }
175 }
176
177 return $size_of_status > 1 ? $counts : reset( $counts );
178 }
179
180 /**
181 * Format price with currency and other settings.
182 *
183 * @param float $price
184 * @param string $currency
185 *
186 * @return string
187 */
188 function learn_press_format_price( $price = 0, $currency = '' ): string {
189 if ( ! is_numeric( $price ) ) {
190 $price = 0;
191 }
192
193 $before = $after = '';
194
195 $currency = esc_html(
196 is_string( $currency ) && '' !== $currency
197 ? $currency
198 : learn_press_get_currency_symbol()
199 );
200 $thousands_separator = esc_html( LP_Settings::get_option( 'thousands_separator', ',' ) );
201 $number_of_decimals = esc_html( LP_Settings::get_option( 'number_of_decimals', 2 ) );
202 $decimals_separator = esc_html( LP_Settings::get_option( 'decimals_separator', '.' ) );
203
204 switch ( LP_Settings::get_option( 'currency_pos' ) ) {
205 default:
206 $before = $currency;
207 break;
208 case 'left_with_space':
209 $before = $currency . ' ';
210 break;
211 case 'right':
212 $after = $currency;
213 break;
214 case 'right_with_space':
215 $after = ' ' . $currency;
216 }
217
218 return $before . number_format( $price, $number_of_decimals, $decimals_separator, $thousands_separator ) . $after;
219 }
220
221 /**
222 * Update
223 *
224 * @param $order_id
225 *
226 * @return array|bool
227 */
228 function learn_press_update_order_items( $order_id ) {
229 $order = learn_press_get_order( $order_id );
230 if ( ! $order ) {
231 return false;
232 }
233
234 $subtotal = 0;
235 $total = 0;
236 $items = $order->get_items();
237
238 if ( $items ) {
239 foreach ( $items as $item ) {
240 $subtotal += $item['subtotal'];
241 $total += $item['total'];
242 }
243 }
244
245 update_post_meta( $order_id, '_order_currency', learn_press_get_currency() );
246 update_post_meta( $order_id, '_prices_include_tax', 'no' );
247 update_post_meta( $order_id, '_order_subtotal', $subtotal );
248 update_post_meta( $order_id, '_order_total', $total );
249 update_post_meta( $order_id, '_order_key', learn_press_generate_order_key() );
250 update_post_meta( $order_id, '_payment_method', '' );
251 update_post_meta( $order_id, '_payment_method_title', '' );
252 update_post_meta( $order_id, '_order_version', '1.0' );
253
254 return array(
255 'subtotal' => $subtotal,
256 'total' => $total,
257 'currency' => learn_press_get_currency(),
258 );
259 }
260
261 /**
262 * Format order's ID in ten numbers. Eg: 0000000XXX.
263 *
264 * @param int $order_number
265 *
266 * @since 2.0.0
267 *
268 * @return string
269 */
270 function learn_press_transaction_order_number( $order_number ) {
271 $formatted_number = apply_filters( 'learn_press_get_order_number', '#' . sprintf( "%'.010d", $order_number ), $order_number );
272
273 return apply_filters( 'learn-press/order-number-formatted', $formatted_number, $order_number );
274 }
275
276 /**
277 * Get list of registered order's statues for registering with wp post's status.
278 *
279 * @since 2.0.0
280 *
281 * @return array
282 */
283 function learn_press_get_register_order_statuses() {
284 $order_statues = array();
285
286 $order_statues['lp-completed'] = array(
287 'label' => _x( 'Completed', 'Order status', 'learnpress' ),
288 'public' => false,
289 'exclude_from_search' => false,
290 'show_in_admin_all_list' => true,
291 'show_in_admin_status_list' => true,
292 'label_count' => _n_noop( 'Completed <span class="count">(%s)</span>', 'Completed <span class="count">(%s)</span>', 'learnpress' ),
293 );
294 $order_statues['lp-pending'] = array(
295 'label' => _x( 'Pending', 'Order status', 'learnpress' ),
296 'public' => false,
297 'exclude_from_search' => false,
298 'show_in_admin_all_list' => true,
299 'show_in_admin_status_list' => true,
300 'label_count' => _n_noop( 'Pending Payment <span class="count">(%s)</span>', 'Pending Payment <span class="count">(%s)</span>', 'learnpress' ),
301 );
302 $order_statues['lp-processing'] = array(
303 'label' => _x( 'Processing', 'Order status', 'learnpress' ),
304 'public' => false,
305 'exclude_from_search' => false,
306 'show_in_admin_all_list' => true,
307 'show_in_admin_status_list' => true,
308 'label_count' => _n_noop( 'Processing <span class="count">(%s)</span>', 'Processing <span class="count">(%s)</span>', 'learnpress' ),
309 );
310 $order_statues['lp-cancelled'] = array(
311 'label' => _x( 'Cancelled', 'Order status', 'learnpress' ),
312 'public' => false,
313 'exclude_from_search' => false,
314 'show_in_admin_all_list' => true,
315 'show_in_admin_status_list' => true,
316 'label_count' => _n_noop( 'Cancelled <span class="count">(%s)</span>', 'Cancelled <span class="count">(%s)</span>', 'learnpress' ),
317 );
318 $order_statues['lp-failed'] = array(
319 'label' => _x( 'Failed', 'Order status', 'learnpress' ),
320 'public' => false,
321 'exclude_from_search' => false,
322 'show_in_admin_all_list' => true,
323 'show_in_admin_status_list' => true,
324 'label_count' => _n_noop( 'Failed <span class="count">(%s)</span>', 'Failed <span class="count">(%s)</span>', 'learnpress' ),
325 );
326 $order_statues['lp-refunded'] = array(
327 'label' => _x( 'Refunded', 'Order status', 'learnpress' ),
328 'public' => false,
329 'exclude_from_search' => false,
330 'show_in_admin_all_list' => true,
331 'show_in_admin_status_list' => true,
332 'label_count' => _n_noop( 'Refunded <span class="count">(%s)</span>', 'Refunded <span class="count">(%s)</span>', 'learnpress' ),
333 );
334 $order_statues['trash'] = array(
335 'label' => _x( 'Trash', 'Order status', 'learnpress' ),
336 'public' => false,
337 'exclude_from_search' => false,
338 'show_in_admin_all_list' => true,
339 'show_in_admin_status_list' => true,
340 'label_count' => _n_noop( 'Trash <span class="count">(%s)</span>', 'Trash <span class="count">(%s)</span>', 'learnpress' ),
341 );
342
343 return $order_statues;
344 }
345
346 function _learn_press_get_order_status_description( $status ) {
347
348 $status = str_replace( 'lp-', '', (string) $status );
349 $descriptions = array(
350 'pending' => __( 'Order received in case a user purchases a course but doesn\'t finalize the order.', 'learnpress' ),
351 'processing' => __( 'Payment received and the order is awaiting fulfillment.', 'learnpress' ),
352 'completed' => __( 'The order is fulfilled and completed.', 'learnpress' ),
353 'cancelled' => __( 'The order is cancelled by an admin or the customer.', 'learnpress' ),
354 'refunded' => __( 'Order was refunded to the customer.', 'learnpress' ),
355 );
356
357 return apply_filters( 'learn_press_order_status_description', ! empty( $descriptions[ $status ] ) ? $descriptions[ $status ] : '' );
358 }
359 /**
360 * Get status of an order by the ID.
361 *
362 * @param int $order_id
363 *
364 * @return bool|string
365 */
366 function learn_press_get_order_status( $order_id ) {
367
368 $order = learn_press_get_order( $order_id );
369
370 if ( $order ) {
371 return $order->get_status();
372 }
373
374 return false;
375 }
376
377 if ( ! function_exists( 'learn_press_get_refund_setting' ) ) {
378 /**
379 * Get refund setting value with safe defaults.
380 *
381 * @param string $key
382 * @param mixed $default
383 *
384 * @since 4.3.4
385 * @version 1.0.0
386 * @return mixed
387 */
388 function learn_press_get_refund_setting( string $key, $default = null ) {
389 $defaults = array(
390 'enable_refund_requests' => 'no',
391 'auto_refund' => 'no',
392 'refund_time_limit' => 30,
393 'require_refund_reason' => 'no',
394 'allow_resend_after_rejected' => 'no',
395 'refund_max_completion' => 0,
396 );
397
398 if ( null === $default && array_key_exists( $key, $defaults ) ) {
399 $default = $defaults[ $key ];
400 }
401
402 return LP_Settings::get_option( $key, $default );
403 }
404 }
405
406 if ( ! function_exists( 'learn_press_get_order_refund_supported_gateways' ) ) {
407 /**
408 * Get list gateways that support refund.
409 *
410 * @since 4.3.4
411 * @version 1.1.0
412 * @return array
413 */
414 function learn_press_get_order_refund_supported_gateways(): array {
415 if ( ! class_exists( 'LP_Gateways' ) ) {
416 return array();
417 }
418
419 $gateways_instance = LP_Gateways::instance();
420 if ( ! $gateways_instance || ! method_exists( $gateways_instance, 'get_gateways' ) ) {
421 return array();
422 }
423
424 $supported_gateways = array();
425 $all_gateways = (array) $gateways_instance->get_gateways();
426 foreach ( $all_gateways as $gateway_id => $gateway ) {
427 $gateway_id = sanitize_key( (string) $gateway_id );
428 if ( empty( $gateway_id ) ) {
429 continue;
430 }
431
432 if ( ! is_object( $gateway ) || ! is_callable( array( $gateway, 'refund' ) ) ) {
433 continue;
434 }
435
436 $supported_gateways[] = $gateway_id;
437 }
438
439 return array_values( array_unique( $supported_gateways ) );
440 }
441 }
442
443 if ( ! function_exists( 'learn_press_cancel_order_process' ) ) {
444 /**
445 * Process action allows user to cancel an order is pending
446 * in their profile.
447 */
448 function learn_press_cancel_order_process() {
449
450 if ( empty( $_REQUEST['cancel-order'] ) || empty( $_REQUEST['lp-nonce'] ) ||
451 ! wp_verify_nonce( $_REQUEST['lp-nonce'], 'cancel-order' ) || is_admin() ) {
452 return;
453 }
454
455 $user_id = get_current_user_id();
456 $profile = LP_Profile::instance( $user_id );
457 $url = $profile->get_tab_link(
458 LP_Settings::instance()->get( 'profile_endpoints.orders', 'orders' )
459 );
460
461 try {
462 $message = array(
463 'status' => 'error',
464 'content' => '',
465 );
466
467 $order_id = absint( $_REQUEST['cancel-order'] );
468 $order = learn_press_get_order( $order_id );
469
470 if ( ! $order ) {
471 throw new Exception( sprintf( __( 'Order number <strong>%s</strong> not found', 'learnpress' ), $order_id ) );
472 }
473
474 $user_ids = (array) $order->get_user_id();
475 if ( ! in_array( $user_id, $user_ids ) ) {
476 throw new Exception( __( 'You do not have permission to cancel this order.', 'learnpress' ) );
477 }
478
479 if ( $order->has_status( LP_ORDER_PENDING ) ) {
480 $order->update_status( LP_ORDER_CANCELLED );
481 $order->add_note( __( 'The order is cancelled by the customer', 'learnpress' ) );
482
483 $message['status'] = 'success';
484 $message['content'] = sprintf( __( 'Order number <strong>%s</strong> has been cancelled', 'learnpress' ), $order->get_order_number() );
485 } else {
486 throw new Exception(
487 __( 'The order number <strong>%s</strong> can not be cancelled.', 'learnpress' ),
488 $order->get_order_number()
489 );
490 }
491 } catch ( Throwable $e ) {
492 $message['content'] = $e->getMessage();
493 }
494
495 learn_press_set_message( $message );
496 wp_safe_redirect( $url );
497 exit();
498 }
499 }
500 add_action( 'init', 'learn_press_cancel_order_process' );
501 if ( ! function_exists( 'learn_press_get_order_refund_event_data' ) ) {
502 /**
503 * Build normalized refund event payload.
504 *
505 * @since 4.3.5
506 * @version 1.0.1
507 *
508 * @param LP_Order $order
509 * @param array $overrides
510 *
511 * @return array
512 */
513 function learn_press_get_order_refund_event_data( LP_Order $order, array $overrides = array() ): array {
514 $order_id = $order->get_id();
515 $requested_at = get_post_meta( $order_id, '_lp_refund_requested_at', true );
516 if ( ! empty( $requested_at ) ) {
517 $requested_at = new LP_Datetime( $requested_at );
518 $requested_at = sprintf(
519 esc_html__( '%1$s %2$s', 'learnpress' ),
520 $requested_at->format( LP_Datetime::I18N_FORMAT_HAS_TIME ),
521 LP_Datetime::get_timezone_string()
522 );
523 }
524
525 $user_id = (int) $order->get_user_id();
526 $userOrderModel = UserModel::find( $user_id, true );
527 if ( $userOrderModel instanceof UserModel ) {
528 $requested_by = $userOrderModel->get_display_name();
529 } else {
530 $requested_by = $order->get_checkout_email();
531 }
532
533 $data = array(
534 'order_id' => $order_id,
535 'order_number' => $order->get_order_number(),
536 'order_key' => $order->get_order_key(),
537 'order_status' => $order->get_status(),
538 'request_status' => $order->get_refund_request(),
539 'requested_by' => $requested_by,
540 'requested_at' => $requested_at,
541 'reviewed_by' => absint( get_post_meta( $order_id, '_lp_refund_reviewed_by', true ) ),
542 'reviewed_at' => (string) get_post_meta( $order_id, '_lp_refund_reviewed_at', true ),
543 'reason' => (string) get_post_meta( $order_id, LP_Order::META_KEY_REFUND_REQUEST_REASON, true ),
544 'requester_email' => '',
545 'admin_order_edit_url' => add_query_arg(
546 array(
547 'post' => $order_id,
548 'action' => 'edit',
549 ),
550 admin_url( 'post.php' )
551 ),
552 );
553
554 $requested_user_id = absint( $data['requested_by'] );
555 if ( ! empty( $requested_user_id ) ) {
556 $requested_user = get_user_by( 'id', $requested_user_id );
557 if ( $requested_user instanceof WP_User ) {
558 $data['requester_email'] = $requested_user->user_email;
559 }
560 }
561
562 if ( ! empty( $overrides ) ) {
563 $data = array_merge( $data, $overrides );
564 }
565
566 return $data;
567 }
568 }
569 /**
570 * Render pending refund request panel on order detail.
571 *
572 * @since 4.3.4
573 * @version 1.0.0
574 * @param LP_Order $order
575 */
576 function learn_press_admin_order_refund_request_panel( $order ) {
577
578 if ( ! $order instanceof LP_Order ) {
579 return;
580 }
581
582 $order_id = $order->get_id();
583 if ( ! current_user_can( 'edit_post', $order_id ) ) {
584 return;
585 }
586
587 $refund_event_data = learn_press_get_order_refund_event_data( $order );
588 $refund_request_status = sanitize_key( (string) ( $refund_event_data['request_status'] ?? '' ) );
589 $requested_by = $refund_event_data['requested_by'] ?? '';
590 $requested_at = $refund_event_data['requested_at'] ?? '';
591 $refund_reason = $refund_event_data['reason'] ?? '';
592 $requester_email = '';
593
594 $status_labels = array(
595 'pending' => __( 'Pending review', 'learnpress' ),
596 'approved' => __( 'Approved', 'learnpress' ),
597 'auto-approved' => __( 'Auto approved', 'learnpress' ),
598 'rejected' => __( 'Rejected', 'learnpress' ),
599 );
600 $status_label = $status_labels[ $refund_request_status ] ?? '';
601 if ( empty( $status_label ) && ! empty( $refund_request_status ) ) {
602 $status_label = ucwords( str_replace( '-', ' ', $refund_request_status ) );
603 }
604
605 $render_statuses = array( 'pending', 'approved', 'auto-approved' );
606 if ( ! in_array( $refund_request_status, $render_statuses, true ) ) {
607 return;
608 }
609
610 $is_pending = ( 'pending' === $refund_request_status );
611 $currency_symbol = learn_press_get_currency_symbol( $order->get_currency() );
612
613 $order_total = round( max( 0, floatval( $order->get_total() ) ), 2 );
614 $order_total_formatted = learn_press_format_price( $order_total, $currency_symbol );
615
616 // Refund amount is only meaningful once the refund has been executed
617 // (approved/auto-approved), when _lp_refund_amount has been written.
618 $refund_amount_formatted = '';
619 if ( ! $is_pending ) {
620 $refund_amount = get_post_meta( $order_id, LP_Order::META_KEY_REFUNDED_AMOUNT, true );
621 if ( '' === $refund_amount || null === $refund_amount ) {
622 $refund_amount = $order_total; // Fallback for full refunds without a stored amount.
623 }
624 $refund_amount = round( max( 0, floatval( $refund_amount ) ), 2 );
625 $refund_amount_formatted = learn_press_format_price( $refund_amount, $currency_symbol );
626 }
627
628 learn_press_admin_view(
629 'meta-boxes/order/refund-request-panel',
630 compact(
631 'order_id',
632 'is_pending',
633 'order_total',
634 'order_total_formatted',
635 'refund_amount_formatted',
636 'requested_by',
637 'requested_at',
638 'requester_email',
639 'refund_reason',
640 'status_label'
641 )
642 );
643 }
644
645 /**
646 * get total price order complete
647 */
648 function learn_press_get_total_price_order_complete() {
649 global $wpdb;
650
651 $query = $wpdb->prepare(
652 "SELECT SUM(meta_value) as order_total From `{$wpdb->prefix}postmeta` as mt
653 INNER JOIN `{$wpdb->prefix}posts` as p ON p.id = mt.post_id
654 WHERE p.post_type = %s AND mt.meta_key = %s
655 AND p.post_status = %s
656 ",
657 LP_ORDER_CPT,
658 '_order_total',
659 'lp-completed'
660 );
661
662 $total = $wpdb->get_results( $query )[0]->order_total;
663
664 return learn_press_format_price( $total, true );
665 }
666