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