PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.9
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.9
4.4.9 4.4.8 4.4.7 4.4.6 4.4.5 4.4.4 4.4.3 4.4.2 4.4.1 4.4.0 4.3.9.1 4.3.9 4.3.8 4.3.7 4.1.6.9 4.1.6.9.1 4.1.6.9.2 4.1.6.9.3 4.1.6.9.4 4.1.7 4.1.7.1 4.1.7.2 4.1.7.3 4.1.7.3.1 4.1.7.3.2 All 140 releases
← All changes | inc/jwt/rest-api/version1/class-lp-rest-courses-v1-controller.php +1768 -1703 4.3.8 → 4.4.9 View file →
@@ -1,1703 +1,1768 @@
1 -<?php
2 -
3 -use LearnPress\Models\CourseModel;
4 -use LearnPress\Models\CoursePostModel;
5 -use LearnPress\Models\Courses;
6 -use LearnPress\Models\UserItems\UserCourseModel;
7 -use LearnPress\Models\UserModel;
8 -
9 -class LP_Jwt_Courses_V1_Controller extends LP_REST_Jwt_Posts_Controller {
10 - protected $namespace = 'learnpress/v1';
11 -
12 - protected $rest_base = 'courses';
13 -
14 - protected $post_type = LP_COURSE_CPT;
15 -
16 - protected $hierarchical = true;
17 -
18 - public function register_routes() {
19 - register_rest_route(
20 - $this->namespace,
21 - '/' . $this->rest_base,
22 - array(
23 - array(
24 - 'methods' => WP_REST_Server::READABLE,
25 - 'callback' => array( $this, 'get_courses' ),
26 - 'permission_callback' => array( $this, 'get_items_permissions_check' ),
27 - 'args' => $this->get_collection_params(),
28 - ),
29 - array(
30 - 'methods' => WP_REST_Server::CREATABLE,
31 - 'callback' => array( $this, 'create_item' ),
32 - 'permission_callback' => array( $this, 'create_item_permissions_check' ),
33 - 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
34 - ),
35 - 'schema' => array( $this, 'get_public_item_schema' ),
36 - )
37 - );
38 -
39 - register_rest_route(
40 - $this->namespace,
41 - '/' . $this->rest_base . '/(?P<id>[\d]+)',
42 - array(
43 - 'args' => array(
44 - 'id' => array(
45 - 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
46 - 'type' => 'integer',
47 - ),
48 - ),
49 - array(
50 - 'methods' => WP_REST_Server::READABLE,
51 - 'callback' => array( $this, 'get_item' ),
52 - 'permission_callback' => array( $this, 'get_item_permissions_check' ),
53 - 'args' => array(
54 - 'context' => $this->get_context_param(
55 - array(
56 - 'default' => 'view',
57 - )
58 - ),
59 - ),
60 - ),
61 - 'schema' => array( $this, 'get_public_item_schema' ),
62 - )
63 - );
64 -
65 - register_rest_route(
66 - $this->namespace,
67 - '/' . $this->rest_base . '/enroll',
68 - array(
69 - 'args' => array(
70 - 'id' => array(
71 - 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
72 - 'type' => 'integer',
73 - ),
74 - ),
75 - array(
76 - 'methods' => WP_REST_Server::CREATABLE,
77 - 'callback' => array( $this, 'enroll_course' ),
78 - 'permission_callback' => array( $this, 'get_course_need_login_check' ),
79 - 'args' => array(
80 - 'context' => $this->get_context_param(
81 - array(
82 - 'default' => 'edit',
83 - )
84 - ),
85 - ),
86 - ),
87 - )
88 - );
89 -
90 - register_rest_route(
91 - $this->namespace,
92 - '/' . $this->rest_base . '/finish',
93 - array(
94 - 'args' => array(
95 - 'id' => array(
96 - 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
97 - 'type' => 'integer',
98 - ),
99 - ),
100 - array(
101 - 'methods' => WP_REST_Server::CREATABLE,
102 - 'callback' => array( $this, 'finish_course' ),
103 - 'permission_callback' => '__return_true',
104 - 'args' => array(
105 - 'context' => $this->get_context_param(
106 - array(
107 - 'default' => 'edit',
108 - )
109 - ),
110 - ),
111 - ),
112 - )
113 - );
114 -
115 - register_rest_route(
116 - $this->namespace,
117 - '/' . $this->rest_base . '/retake',
118 - array(
119 - 'args' => array(
120 - 'id' => array(
121 - 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
122 - 'type' => 'integer',
123 - ),
124 - ),
125 - array(
126 - 'methods' => WP_REST_Server::CREATABLE,
127 - 'callback' => array( $this, 'retake_course' ),
128 - 'permission_callback' => '__return_true',
129 - 'args' => array(
130 - 'context' => $this->get_context_param(
131 - array(
132 - 'default' => 'edit',
133 - )
134 - ),
135 - ),
136 - ),
137 - )
138 - );
139 -
140 - register_rest_route(
141 - $this->namespace,
142 - '/' . $this->rest_base . '/verify-receipt',
143 - array(
144 - 'methods' => WP_REST_Server::CREATABLE,
145 - 'callback' => array( $this, 'verify_receipt' ),
146 - 'permission_callback' => '__return_true',
147 - 'args' => array(
148 - 'context' => $this->get_context_param(
149 - array(
150 - 'default' => 'edit',
151 - )
152 - ),
153 - 'receipt-data' => array(
154 - 'description' => esc_html__( 'Receipt data.', 'learnpress' ),
155 - 'type' => 'string',
156 - ),
157 - ),
158 - )
159 - );
160 - }
161 -
162 - public function get_items_permissions_check( $request ) {
163 - $post_type = get_post_type_object( $this->post_type );
164 -
165 - if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
166 - return new WP_Error(
167 - 'rest_forbidden_context',
168 - __( 'Sorry, you are not allowed to edit posts in this post type.' ),
169 - array( 'status' => rest_authorization_required_code() )
170 - );
171 - }
172 -
173 - return true;
174 - }
175 -
176 - public function get_course_need_login_check( $request ) {
177 - if ( ! is_user_logged_in() ) {
178 - return new WP_Error(
179 - 'rest_forbidden_context',
180 - __( 'Please login to continue' ),
181 - array( 'status' => 401 )
182 - );
183 - }
184 -
185 - return true;
186 - }
187 -
188 - /**
189 - * Checks if a course can be read.
190 - *
191 - * Correctly handles courses with the inherit status.
192 - *
193 - * @return bool Whether the post can be read.
194 - * *@author Nhamdv
195 - *
196 - */
197 - public function check_read_permission( $post_id ) {
198 - if ( empty( absint( $post_id ) ) ) {
199 - return false;
200 - }
201 -
202 - $post = get_post( $post_id );
203 -
204 - if ( ! $post ) {
205 - return false;
206 - }
207 -
208 - // Is the post readable?
209 - if ( 'publish' === $post->post_status || current_user_can( 'read_post', $post->ID ) ) {
210 - return true;
211 - }
212 -
213 - $post_status_obj = get_post_status_object( $post->post_status );
214 - if ( $post_status_obj && $post_status_obj->public ) {
215 - return true;
216 - }
217 -
218 - // Can we read the parent if we're inheriting?
219 - if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) {
220 - $parent = get_post( $post->post_parent );
221 -
222 - if ( $parent ) {
223 - return $this->check_read_permission( $parent );
224 - }
225 - }
226 -
227 - /*
228 - * If there isn't a parent, but the status is set to inherit, assume
229 - * it's published (as per get_post_status()).
230 - */
231 - if ( 'inherit' === $post->post_status ) {
232 - return true;
233 - }
234 -
235 - return false;
236 - }
237 -
238 - protected function get_object( $course = 0 ) {
239 - global $post;
240 -
241 - if ( false === $course && isset( $post, $post->ID ) && LP_COURSE_CPT === get_post_type( $post->ID ) ) {
242 - $id = absint( $post->ID );
243 - } elseif ( is_numeric( $course ) ) {
244 - $id = $course;
245 - } elseif ( $course instanceof LP_Course ) {
246 - $id = $course->get_id();
247 - } elseif ( ! empty( $course->ID ) ) {
248 - $id = $course->ID;
249 - }
250 -
251 - return learn_press_get_course( $id );
252 - }
253 -
254 - public function verify_receipt( $request ) {
255 - $response = new LP_REST_Response();
256 - $receipt = ! empty( $request['receipt-data'] ) ? $request['receipt-data'] : '';
257 - $is_ios = ! empty( $request['is-ios'] ) ? true : false;
258 -
259 - // Get request header.
260 - $platform = ! empty( $request->get_header( 'x-platform' ) ) ? sanitize_text_field( $request->get_header( 'x-platform' ) ) : '';
261 - $password = function_exists( 'learnpress_mobile_iap_settings' ) ? learnpress_mobile_iap_settings( $platform )['apple_token_secret'] : LP_Settings::instance()->get( 'in_app_purchase_apple_shared_secret', '' );
262 -
263 - try {
264 - if ( empty( $receipt ) ) {
265 - throw new Exception( __( 'Receipt data is empty.', 'learnpress' ) );
266 - }
267 -
268 - if ( $is_ios ) {
269 - $course_id = ! empty( $request['course-id'] ) ? absint( $request['course-id'] ) : 0;
270 -
271 - if ( empty( $password ) ) {
272 - throw new Exception( __( 'The secret key is empty.', 'learnpress' ) );
273 - }
274 -
275 - $url = LP_Settings::instance()->get( 'in_app_purchase_apple_sandbox' ) === 'yes' ? 'https://sandbox.itunes.apple.com/verifyReceipt' : 'https://buy.itunes.apple.com/verifyReceipt';
276 -
277 - $verify = wp_remote_post(
278 - $url,
279 - array(
280 - 'method' => 'POST',
281 - 'timeout' => 60,
282 - 'body' => wp_json_encode(
283 - array(
284 - 'receipt-data' => $receipt,
285 - 'password' => $password,
286 - )
287 - ),
288 - )
289 - );
290 -
291 - if ( is_wp_error( $verify ) ) {
292 - throw new Exception( $verify->get_error_message() );
293 - }
294 -
295 - $body = json_decode( wp_remote_retrieve_body( $verify ) );
296 -
297 - if ( $body->status !== 0 ) {
298 - throw new Exception( __( 'Cannot verify the receipt', 'learnpress' ) );
299 - }
300 -
301 - $latest_receipt_info = ! empty( $body->latest_receipt_info ) ? $body->latest_receipt_info : array();
302 -
303 - if ( empty( $latest_receipt_info ) ) {
304 - throw new Exception( __( 'The course ID is invalid.', 'learnpress' ) );
305 - }
306 -
307 - $course_ids = array_map(
308 - function ( $receipt_id ) {
309 - return absint( $receipt_id->product_id );
310 - },
311 - $latest_receipt_info
312 - );
313 -
314 - if ( ! in_array( $course_id, $course_ids ) ) {
315 - throw new Exception( __( 'The course ID is invalid.', 'learnpress' ) );
316 - }
317 - } else {
318 - $receipt = json_decode( $receipt, true );
319 - $package_name = $receipt['packageName'] ?? '';
320 - $course_id = ! empty( $receipt['productId'] ) ? absint( $receipt['productId'] ) : 0;
321 - $purchase_token = $receipt['purchaseToken'] ?? '';
322 -
323 - if ( ! function_exists( 'learnpress_in_app_purchase_get_access_token' ) ) {
324 - throw new Exception( __( 'Cannot verify the receipt', 'learnpress' ) );
325 - }
326 -
327 - $access_token = function_exists( 'learnpress_mobile_iap_settings' ) ? learnpress_in_app_purchase_get_access_token( $platform ) : learnpress_in_app_purchase_get_access_token();
328 -
329 - $verify = wp_remote_get(
330 - 'https://androidpublisher.googleapis.com/androidpublisher/v3/applications/' . $package_name . '/purchases/products/' . $course_id . '/tokens/' . $purchase_token,
331 - array(
332 - 'headers' => array(
333 - 'Content-Type' => 'application/json',
334 - 'Authorization' => 'Bearer ' . $access_token,
335 - ),
336 - 'timeout' => 60,
337 - )
338 - );
339 -
340 - $body = json_decode( wp_remote_retrieve_body( $verify ) );
341 -
342 - if ( isset( $body->error->message ) ) {
343 - throw new Exception( $body->error->message );
344 - }
345 - }
346 -
347 - $course = learn_press_get_course( $course_id );
348 -
349 - if ( ! $course ) {
350 - throw new Exception( __( 'The course ID is invalid.', 'learnpress' ) );
351 - }
352 - $user = learn_press_get_current_user();
353 -
354 - $cart = LearnPress::instance()->cart;
355 - $checkout = LP_Checkout::instance();
356 -
357 - if ( ! learn_press_enable_cart() ) {
358 - // $order_awaiting_payment = LearnPress::instance()->session->get( 'order_awaiting_payment' );
359 - $cart->empty_cart();
360 - // LearnPress::instance()->session->set( 'order_awaiting_payment', $order_awaiting_payment );
361 - }
362 -
363 - $cart_id = $cart->add_to_cart( $course_id, 1, array() );
364 -
365 - if ( ! $cart_id ) {
366 - throw new Exception( esc_html__( 'Error: The course cannot be added to the cart.', 'learnpress' ) );
367 - }
368 -
369 - if ( is_user_logged_in() ) {
370 - $order_id = $checkout->create_order();
371 - $order = new LP_Order( $order_id );
372 - $order->payment_complete();
373 - $cart->empty_cart();
374 - }
375 -
376 - $response->status = 'success';
377 - $response->message = esc_html__( 'Verify Receipt Data successfully.', 'learnpress' );
378 - } catch ( \Throwable $th ) {
379 - error_log( $th->getMessage() );
380 - $response->status = 'error';
381 - $response->message = $th->getMessage();
382 - }
383 -
384 - return rest_ensure_response( $response );
385 - }
386 -
387 - /**
388 - * Convert params App to query courses.
389 - *
390 - * @param $params
391 - *
392 - * @return array|mixed
393 - */
394 - public function convert_params_query_courses( $params = [] ) {
395 - $params['limit'] = $params['per_page'] ?? 10;
396 -
397 - $params['paged'] = $params['page'] ?? 1;
398 -
399 - if ( ! empty( $params['popular'] ) ) {
400 - $params['order_by'] = 'popular';
401 - } else {
402 - $params['order_by'] = $params['orderby'] ?? 'menu_order';
403 - switch ( $params['order_by'] ) {
404 - case 'date':
405 - $params['order_by'] = 'post_date';
406 - $params['order'] = $params['order'] ?? 'DESC';
407 - break;
408 - case 'title':
409 - $params['order_by'] = 'post_title';
410 - break;
411 - case 'price':
412 - if ( ! empty( $params['order'] ) && 'asc' === $params['order'] ) {
413 - $params['order_by'] = 'price_low';
414 - }
415 - break;
416 - default:
417 - $params['order'] = $params['order'] ?? 'DESC';
418 - break;
419 - }
420 - }
421 -
422 - if ( ! empty( $params['search'] ) ) {
423 - $params['c_search'] = trim( $params['search'] );
424 - }
425 -
426 - if ( ! empty( $params['user'] ) ) {
427 - $params['c_author'] = $params['user'];
428 - }
429 -
430 - if ( ! empty( $params['on_sale'] ) ) {
431 - $params['sort_by'] = 'on_sale';
432 - }
433 -
434 - $params['return_type'] = 'json';
435 - //$params['c_only_fields'] = empty( $params['c_only_fields'] ) ? '' : explode( ',', $params['c_only_fields'] );
436 - $params['term_id'] = empty( $params['category'] ) || 'all' === $params['category'] ? '' : implode( ',', $params['category'] );
437 -
438 - return $params;
439 - }
440 -
441 - /**
442 - * Get courses.
443 - *
444 - * @param WP_REST_Request $request
445 - *
446 - * @return WP_REST_Response|LP_REST_Response
447 - * @since 4.2.6.9
448 - * @version 1.0.1
449 - */
450 - public function get_courses( WP_REST_Request $request ) {
451 - $res = new LP_REST_Response();
452 - $courses = [];
453 - $total = 0;
454 - $total_pages = 0;
455 - try {
456 - $filter = new LP_Course_Filter();
457 - $params = $request->get_params();
458 - $params = $this->convert_params_query_courses( $params );
459 -
460 - Courses::handle_params_for_query_courses( $filter, $params );
461 - $key_cache = 'api/' . md5( json_encode( $params ) );
462 - $key_cache_total = $key_cache . '_total';
463 - $key_cache_total_pages = $key_cache . '_total_pages';
464 - $lp_courses_cache = new LP_Courses_Cache( true );
465 -
466 - if ( ! empty( $params['learned'] ) ) {
467 - $user_id = get_current_user_id();
468 - // For tst user hard code
469 - // $user_id = ! empty( $params['user_id'] ) ? $params['user_id'] : get_current_user_id();
470 - if ( $user_id <= 0 ) {
471 - $user_id = - 1;
472 - }
473 - // Get courses that user has learned
474 - /*$lp_user_courses = LP_User_Items_DB::getInstance();
475 - $filter_user_course = new LP_User_Items_Filter();
476 - $filter_user_course->only_fields = array( 'DISTINCT(item_id) AS ID' );
477 - $filter_user_course->user_id = $user_id;
478 - $filter_user_course->item_type = LP_COURSE_CPT;
479 - $filter_user_course->return_string_query = true;
480 - $user_courses_query = $lp_user_courses->get_user_items( $filter_user_course );
481 - $params = $lp_user_courses->get_user_items( $filter_user_course );
482 - $filter->where[] = "AND p.ID IN ({$user_courses_query})";*/
483 -
484 - $lp_user_items_db = LP_User_Items_DB::getInstance();
485 - $filter->fields[] = 'ui.user_item_id, ui.user_id, ui.item_id, ui.start_time, ui.end_time, ui.item_type, ui.graduation, ui.status';
486 - $filter->join[] = "LEFT JOIN $lp_user_items_db->tb_lp_user_items AS ui ON p.ID = ui.item_id";
487 - $filter->where[] = $lp_user_items_db->wpdb->prepare( 'AND ui.user_id = %s', $user_id );
488 - $filter->where[] = $lp_user_items_db->wpdb->prepare( 'AND ui.item_type = %s', LP_COURSE_CPT );
489 - if ( ! empty( $params['course_filter'] ) ) {
490 - $filter->where[] = $lp_user_items_db->wpdb->prepare( 'AND ui.graduation = %s', $params['course_filter'] );
491 - }
492 - } else {
493 - // Check cache with case not learned
494 - $courses_cache = $lp_courses_cache->get_cache( $key_cache );
495 - if ( $courses_cache !== false ) {
496 - $courses = json_decode( $courses_cache, true );
497 - $total = (int) $lp_courses_cache->get_cache( $key_cache_total );
498 - $total_pages = (int) $lp_courses_cache->get_cache( $key_cache_total_pages );
499 -
500 - $response = rest_ensure_response( $courses );
501 - $response->header( 'X-WP-Total', $total );
502 - $response->header( 'X-WP-TotalPages', $total_pages );
503 -
504 - return $response;
505 - }
506 - }
507 -
508 - $rs_courses = Courses::get_courses( $filter, $total );
509 - $courses = $this->prepare_struct_courses_response( $rs_courses, $params );
510 - $total_pages = LP_Database::get_total_pages( $filter->limit, $total );
511 - } catch ( Throwable $e ) {
512 - $res->message = $e->getMessage();
513 -
514 - return $res;
515 - }
516 -
517 - $response = rest_ensure_response( $courses );
518 - $response->header( 'X-WP-Total', $total );
519 - $response->header( 'X-WP-TotalPages', $total_pages );
520 -
521 - // Set cache with case not learned
522 - if ( empty( $params['learned'] ) ) {
523 - $lp_courses_cache->set_cache( $key_cache, json_encode( $courses, JSON_UNESCAPED_UNICODE ) );
524 - $lp_courses_cache->save_cache_keys( LP_Courses_Cache::KEYS_QUERY_COURSES_APP, $key_cache );
525 - $lp_courses_cache->set_cache( $key_cache_total, $total );
526 - $lp_courses_cache->save_cache_keys( LP_Courses_Cache::KEYS_QUERY_COURSES_APP, $key_cache_total );
527 - $lp_courses_cache->set_cache( $key_cache_total_pages, $total_pages );
528 - $lp_courses_cache->save_cache_keys( LP_Courses_Cache::KEYS_QUERY_COURSES_APP, $key_cache_total_pages );
529 - }
530 -
531 - return $response;
532 - }
533 -
534 - /**
535 - * @throws Exception
536 - */
537 - public function prepare_struct_courses_response( $courses, $params ): array {
538 - $data = [];
539 - foreach ( $courses as $courseObj ) {
540 - $course = CourseModel::find( $courseObj->ID, true );
541 - if ( empty( $course ) ) {
542 - continue;
543 - }
544 -
545 - $courseObjPrepare = new stdClass();
546 - $courseObjPrepare->id = (int) $courseObj->ID ?? 0;
547 - $courseObjPrepare->name = html_entity_decode( $course->get_title() );
548 - $courseObjPrepare->image = $course->get_image_url();
549 - $author = $course->get_author_model();
550 - $courseObjPrepare->instructor = ! empty( $author ) ? $this->get_author_info( $author ) : [];
551 - $duration = $course->get_meta_value_by_key( CoursePostModel::META_KEY_DURATION, '' );
552 - $duration_arr = explode( ' ', $duration );
553 - $duration_number = floatval( $duration_arr[0] ?? 0 );
554 - $duration_type = $duration_arr[1] ?? '';
555 - if ( empty( $duration_number ) ) {
556 - $duration_str = __( 'Lifetime', 'learnpress' );
557 - } else {
558 - $duration_str = LP_Datetime::get_string_plural_duration( $duration_number, $duration_type );
559 - }
560 - $courseObjPrepare->duration = $duration_str;
561 - $course_categories = $course->get_categories();
562 - // Add key id, for old app.
563 - foreach ( $course_categories as $k => $category ) {
564 - $category->id = $category->term_id;
565 - $course_categories[ $k ] = $category;
566 - }
567 - $courseObjPrepare->categories = $course_categories;
568 - $courseObjPrepare->price = $course->get_price();
569 - $courseObjPrepare->price_rendered = $this->render_course_price( $course );
570 - $courseObjPrepare->origin_price = $course->get_regular_price();
571 - $courseObjPrepare->origin_price_rendered = html_entity_decode(
572 - learn_press_format_price( $courseObjPrepare->origin_price, true )
573 - );
574 - $courseObjPrepare->on_sale = $course->has_sale_price();
575 - $courseObjPrepare->sale_price = (float) $course->get_sale_price();
576 - $courseObjPrepare->sale_price_rendered = html_entity_decode(
577 - learn_press_format_price( $course->get_sale_price(), true )
578 - );
579 - // When release Addon Course Review v4.1.3 a long time, we will remove this code.
580 - $courseObjPrepare->rating = $this->get_course_rating( $courseObj->ID );
581 - $courseObjPrepare->meta_data = new stdClass();
582 - $courseObjPrepare->meta_data->_lp_passing_condition = $course->get_meta_value_by_key( CoursePostModel::META_KEY_PASSING_CONDITION );
583 -
584 - // Add more fields
585 - if ( ! empty( $params['learned'] ) ) {
586 - $courseObjPrepare->course_data = new stdClass();
587 - $courseObjPrepare->course_data->user_item_id = $courseObj->user_item_id ?? 0;
588 - $courseObjPrepare->course_data->user_id = $courseObj->user_id ?? 0;
589 - $courseObjPrepare->course_data->item_id = $courseObj->item_id ?? 0;
590 - $courseObjPrepare->course_data->item_type = $courseObj->item_type ?? 0;
591 - $courseObjPrepare->course_data->status = $courseObj->status ?? '';
592 - $courseObjPrepare->course_data->graduation = $courseObj->graduation ?? '';
593 - $courseObjPrepare->course_data->start_time = $courseObj->start_time ?? '';
594 - $courseObjPrepare->course_data->end_time = $courseObj->end_time ?? '';
595 -
596 - $userCourse = new UserCourseModel( $courseObjPrepare->course_data );
597 - $expirationTime = $userCourse->get_expiration_time();
598 - $courseObjPrepare->course_data->expiration_time = $expirationTime ? $expirationTime->format( LP_Datetime::I18N_FORMAT ) : __( 'Lifetime', 'learnpress' );
599 - $courseObjPrepare->course_data->result = $userCourse->calculate_course_results();
600 - }
601 -
602 - $courseObjPrepare = apply_filters( 'learnPress/prepare_struct_courses_response/courseObjPrepare', $courseObjPrepare, $course );
603 - $data[] = $courseObjPrepare;
604 - }
605 -
606 - return $data;
607 - }
608 -
609 - public function enroll_course( $request ) {
610 - if ( ! class_exists( 'LP_REST_Courses_Controller' ) ) {
611 - include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-courses-controller.php';
612 - }
613 -
614 - $course_controller = new LP_REST_Courses_Controller();
615 -
616 - return $course_controller->enroll_courses( $request );
617 - }
618 -
619 - public function finish_course( $request ) {
620 - $response = new LP_REST_Response();
621 -
622 - try {
623 - $user = learn_press_get_current_user();
624 - $course_id = isset( $request['id'] ) ? wp_unslash( $request['id'] ) : false;
625 -
626 - if ( empty( $course_id ) ) {
627 - throw new Exception( esc_html__( 'Error: No Course ID available.', 'learnpress' ) );
628 - }
629 -
630 - $course = learn_press_get_course( $course_id );
631 - $check = $user->can_show_finish_course_btn( $course );
632 -
633 - if ( $check['status'] !== 'success' ) {
634 - throw new Exception( $check['message'] ?? esc_html__( 'Cannot finish this course.', 'learnpress' ) );
635 - }
636 -
637 - $finished = $user->finish_course( $course_id );
638 -
639 - if ( empty( $finished ) ) {
640 - throw new Exception( esc_html__( 'Error: Cannot finish this course.', 'learnpress' ) );
641 - }
642 -
643 - $response->status = 'success';
644 - $response->message = esc_html__( 'Congrats! You have completed the Course.', 'learnpress' );
645 - } catch ( \Throwable $th ) {
646 - $response->status = 'error';
647 - $response->message = $th->getMessage();
648 - }
649 -
650 - return rest_ensure_response( $response );
651 - }
652 -
653 - public function retake_course( $request ) {
654 - $response = new LP_REST_Response();
655 -
656 - try {
657 - if ( ! class_exists( 'LP_REST_Courses_Controller' ) ) {
658 - include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-courses-controller.php';
659 - }
660 -
661 - $course_controller = new LP_REST_Courses_Controller();
662 - $response = $course_controller->retake_course( $request );
663 - } catch ( Throwable $e ) {
664 - $response->message = $e->getMessage();
665 - }
666 -
667 - return $response;
668 - }
669 -
670 - /**
671 - * Create a single product.
672 - *
673 - * @param WP_REST_Request $request Full details about the request.
674 - *
675 - * @return WP_Error|WP_REST_Response
676 - */
677 - public function create_item( $request ) {
678 - if ( ! empty( $request['id'] ) ) {
679 - return new WP_Error( "lp_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'learnpress' ), $this->post_type ), array( 'status' => 400 ) );
680 - }
681 -
682 - $prepared_post = $this->prepare_item_for_database( $request );
683 -
684 - if ( is_wp_error( $prepared_post ) ) {
685 - return $prepared_post;
686 - }
687 -
688 - $prepared_post->post_type = $this->post_type;
689 -
690 - $post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true, false );
691 -
692 - if ( is_wp_error( $post_id ) ) {
693 - if ( 'db_insert_error' === $post_id->get_error_code() ) {
694 - $post_id->add_data( array( 'status' => 500 ) );
695 - } else {
696 - $post_id->add_data( array( 'status' => 400 ) );
697 - }
698 -
699 - return $post_id;
700 - }
701 -
702 - $post = get_post( $post_id );
703 -
704 - do_action( "lp_rest_insert_{$this->post_type}", $post, $request, true );
705 -
706 - $fields_update = $this->update_additional_fields_for_object( $post, $request );
707 -
708 - if ( is_wp_error( $fields_update ) ) {
709 - return $fields_update;
710 - }
711 -
712 - $request->set_param( 'context', 'edit' );
713 -
714 - do_action( "lp_rest_after_insert_{$this->post_type}", $post, $request, true );
715 -
716 - wp_after_insert_post( $post, false, null );
717 -
718 - $object = $this->get_object( $post_id );
719 -
720 - $response = $this->prepare_object_for_response( $object, $request );
721 - $response = rest_ensure_response( $response );
722 -
723 - $response->set_status( 201 );
724 - $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ) );
725 -
726 - return $response;
727 - }
728 -
729 - public function prepare_object_for_response( $object, $request ) {
730 - $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
731 - $data = $this->get_course_data( $object, $context, $request );
732 -
733 - $response = rest_ensure_response( $data );
734 -
735 - return apply_filters( "lp_jwt_rest_prepare_{$this->post_type}_object", $response, $object, $request );
736 - }
737 -
738 - /**
739 - * @param LP_Course $course
740 - *
741 - * @throws Exception
742 - */
743 - protected function get_course_data( $course, $context = 'view' ) {
744 - $request = func_num_args() >= 2 ? func_get_arg( 2 ) : new WP_REST_Request( '', '', array( 'context' => $context ) );
745 - $fields = $this->get_fields_for_response( $request );
746 -
747 - $id = $course->get_id();
748 - $post = get_post( $course->get_id() );
749 -
750 - $data = array();
751 -
752 - foreach ( $fields as $field ) {
753 - if ( ! empty( $request['optimize'] ) ) {
754 - $disables = is_bool( $request['optimize'] ) ? 'sections,course_data,instructor,meta_data,tags,can_finish,can_retake,count_students,rataken,ratake_count' : $request['optimize'];
755 - $disable = explode( ',', $disables );
756 -
757 - if ( ! empty( $disable ) && in_array( $field, $disable ) ) {
758 - continue;
759 - }
760 - }
761 -
762 - switch ( $field ) {
763 - case 'id':
764 - $data['id'] = $course->get_id();
765 - break;
766 - case 'name':
767 - $data['name'] = $post->post_title;
768 - break;
769 - case 'slug':
770 - $data['slug'] = $post->post_name;
771 - break;
772 - case 'permalink':
773 - $data['permalink'] = $course->get_permalink();
774 - break;
775 - case 'image':
776 - $data['image'] = $course->get_image_url( 'full' );
777 - break;
778 - case 'date_created':
779 - $data['date_created'] = lp_jwt_prepare_date_response( $post->post_date_gmt, $post->post_date );
780 - break;
781 - case 'date_created_gmt':
782 - $data['date_created_gmt'] = lp_jwt_prepare_date_response( $post->post_date_gmt );
783 - break;
784 - case 'date_modified':
785 - $data['date_modified'] = lp_jwt_prepare_date_response( $post->post_modified_gmt, $post->post_modified );
786 - break;
787 - case 'date_modified_gmt':
788 - $data['date_modified_gmt'] = lp_jwt_prepare_date_response( $post->post_modified_gmt );
789 - break;
790 - case 'on_sale':
791 - $data['on_sale'] = $course->has_sale_price();
792 - break;
793 - case 'status':
794 - $data['status'] = $post->post_status;
795 - break;
796 - case 'content':
797 - $data['content'] = 'view' === $context ? apply_filters( 'the_content', $post->post_content ) : $post->post_content;
798 - break;
799 - case 'excerpt':
800 - $data['excerpt'] = $post->post_excerpt;
801 - break;
802 - case 'count_students':
803 - $data['count_students'] = $course->count_students();
804 - break;
805 - case 'can_finish':
806 - $data['can_finish'] = $this->check_can_finish( $course );
807 - break;
808 - case 'can_retake':
809 - $data['can_retake'] = $this->check_can_retake( $id );
810 - break;
811 - case 'ratake_count':
812 - $data['ratake_count'] = (int) $course->get_data( 'retake_count' );
813 - break;
814 - case 'rataken':
815 - $data['rataken'] = $this->get_retaken_count( $id );
816 - break;
817 - case 'duration':
818 - $data['duration'] = learn_press_get_post_translated_duration( $id, esc_html__( 'Lifetime', 'learnpress' ) );
819 - break;
820 - case 'categories':
821 - $data['categories'] = $this->get_course_taxonomy( $id, 'course_category' );
822 - break;
823 - case 'tags':
824 - $data['tags'] = $this->get_course_taxonomy( $id, 'course_tag' );
825 - break;
826 - case 'instructor':
827 - $data['instructor'] = $this->get_instructor_info( $course );
828 - break;
829 - case 'sections':
830 - $data['sections'] = $this->get_all_items( $course );
831 - break;
832 - case 'course_data':
833 - $data['course_data'] = $this->get_course_data_for_current_user( $id, $request );
834 - break;
835 - case 'rating':
836 - $data['rating'] = $this->get_course_rating( $id );
837 - break;
838 - case 'price':
839 - $data['price'] = floatval( $course->get_price() );
840 - break;
841 - case 'price_rendered':
842 - $data['price_rendered'] = html_entity_decode( $course->get_price_html() );
843 - break;
844 - case 'origin_price':
845 - $data['origin_price'] = floatval( $course->get_origin_price() );
846 - break;
847 - case 'origin_price_rendered':
848 - $data['origin_price_rendered'] = html_entity_decode( $course->get_origin_price_html() );
849 - break;
850 - case 'sale_price':
851 - $data['sale_price'] = floatval( $course->get_sale_price() );
852 - break;
853 - case 'sale_price_rendered':
854 - $data['sale_price_rendered'] = html_entity_decode( learn_press_format_price( $course->get_sale_price(), true ) );
855 - break;
856 - }
857 - }
858 -
859 - $data['meta_data'] = $this->get_course_meta( $id );
860 -
861 - return $data;
862 - }
863 -
864 - /**
865 - * Handle price course return to App
866 - *
867 - * @param CourseModel $course
868 - *
869 - * @return string
870 - * @throws Exception
871 - * @version 1.0.0
872 - * @since 4.2.6.9
873 - */
874 - public function render_course_price( CourseModel $course ): string {
875 - $price_string = '';
876 -
877 - if ( $course->is_free() ) {
878 - $price_string = apply_filters(
879 - 'learn_press_course_price_html_free',
880 - esc_html__( 'Free', 'learnpress' ),
881 - $this
882 - );
883 - } else {
884 - $price_string .= learn_press_format_price( $course->get_price() );
885 - }
886 -
887 - return html_entity_decode( $price_string );
888 - }
889 -
890 - public function get_retaken_count( $id ) {
891 - $user_id = get_current_user_id();
892 -
893 - if ( ! $user_id ) {
894 - return 0;
895 - }
896 -
897 - $user = learn_press_get_user( $user_id );
898 -
899 - if ( ! $user ) {
900 - return 0;
901 - }
902 -
903 - $user_course_data = $user->get_course_data( $id );
904 - if ( ! $user_course_data ) {
905 - return 0;
906 - }
907 -
908 - return absint( $user_course_data->get_retaken_count() );
909 - }
910 -
911 - public function check_can_retake( $id ) {
912 - $user_id = get_current_user_id();
913 -
914 - if ( ! $user_id ) {
915 - return 0;
916 - }
917 -
918 - $user = learn_press_get_user( $user_id );
919 -
920 - if ( $user ) {
921 - $can_retake_times = $user->can_retry_course( $id );
922 -
923 - if ( $can_retake_times ) {
924 - return true;
925 - }
926 -
927 - return false;
928 - }
929 -
930 - return false;
931 - }
932 -
933 - public function get_course_rating( $id ) {
934 - if ( ! function_exists( 'learn_press_get_course_rate' ) ) {
935 - return false;
936 - }
937 -
938 - $course_rate = learn_press_get_course_rate( $id );
939 -
940 - return ! empty( $course_rate ) ? floatval( number_format( $course_rate, 1 ) ) : 0;
941 - }
942 -
943 - public function check_can_finish( $course ) {
944 - $user_id = get_current_user_id();
945 -
946 - if ( ! $user_id ) {
947 - return false;
948 - }
949 -
950 - $user = learn_press_get_user( $user_id );
951 -
952 - if ( $user && $course ) {
953 - $check = $user->can_show_finish_course_btn( $course );
954 -
955 - if ( $check['status'] === 'success' ) {
956 - return true;
957 - }
958 -
959 - return false;
960 - }
961 -
962 - return false;
963 - }
964 -
965 - /**
966 - * Get instructor info
967 - *
968 - * @param LP_Course $course
969 - *
970 - * @return array
971 - */
972 - public function get_instructor_info( $course ) {
973 - if ( ! $course ) {
974 - return [];
975 - }
976 -
977 - if ( $course instanceof LP_Course ) {
978 - $courseModel = CourseModel::find( $course->get_id(), true );
979 - } elseif ( $course instanceof CourseModel ) {
980 - $courseModel = $course;
981 - } else {
982 - return [];
983 - }
984 -
985 - $output = array();
986 - $extra_info = learn_press_get_user_extra_profile_info( $courseModel->get_id() );
987 - $author = $courseModel->get_author_model();
988 - if ( ! $author ) {
989 - return [];
990 - }
991 -
992 - $output['avatar'] = $author->get_avatar_url();
993 - $output['id'] = $author->get_id();
994 - $output['name'] = $author->get_display_name();
995 - $output['description'] = $author->get_description();
996 - $output['social'] = $extra_info;
997 -
998 - return $output;
999 - }
1000 -
1001 - /**
1002 - * Get instructor info
1003 - *
1004 - * @param UserModel $author
1005 - *
1006 - * @return array
1007 - */
1008 - public function get_author_info( $author ): array {
1009 - $output = [];
1010 - $output['avatar'] = $author->get_avatar_url();
1011 - $output['id'] = $author->get_id();
1012 - $output['name'] = $author->display_name;
1013 - $output['description'] = $author->get_description();
1014 -
1015 - return $output;
1016 - }
1017 -
1018 - public function get_item_learned_ids( $request ) {
1019 - global $wpdb;
1020 -
1021 - $user_id = get_current_user_id();
1022 -
1023 - if ( ! $user_id ) {
1024 - return false;
1025 - }
1026 -
1027 - $filter = ! empty( $request['course_filter'] ) ? $request['course_filter'] : false;
1028 - $where = $wpdb->prepare( 'user_id=%d AND item_type=%s', $user_id, $this->post_type ); // phpcs:ignore
1029 -
1030 - if ( $filter ) {
1031 - if ( $filter === 'in-progress' ) {
1032 - $where .= $wpdb->prepare( ' AND status=%s AND graduation=%s', 'enrolled', 'in-progress' );
1033 - } elseif ( in_array( $filter, array( 'passed', 'failed' ) ) ) { // is "passed" or "failed"
1034 - $where .= $wpdb->prepare( ' AND status=%s AND graduation=%s', 'finished', $filter );
1035 - }
1036 - }
1037 -
1038 - $query = "SELECT item_id FROM {$wpdb->prefix}learnpress_user_items WHERE {$where}";
1039 -
1040 - $item_ids = $wpdb->get_col( $query );
1041 -
1042 - return $item_ids;
1043 - }
1044 -
1045 - public function get_course_data_for_current_user( $id, $request ) {
1046 - $user = learn_press_get_user( get_current_user_id() );
1047 -
1048 - if ( empty( $user ) || empty( $id ) ) {
1049 - return array();
1050 - }
1051 -
1052 - $course_data = $user->get_course_data( $id );
1053 -
1054 - if ( ! $course_data ) {
1055 - return;
1056 - }
1057 -
1058 - return array(
1059 - 'graduation' => $course_data->get_graduation() ?? '',
1060 - 'status' => $course_data->get_status() ?? '',
1061 - 'start_time' => $course_data->get_start_time() ? lp_jwt_prepare_date_response( $course_data->get_start_time()->toSql() ) : null,
1062 - 'end_time' => $course_data->get_end_time() ? lp_jwt_prepare_date_response( $course_data->get_end_time()->toSql() ) : null,
1063 - 'expiration_time' => $course_data->get_expiration_time() ? lp_jwt_prepare_date_response( $course_data->get_expiration_time()->toSql() ) : '',
1064 - 'result' => $course_data->calculate_course_results(),
1065 - );
1066 - }
1067 -
1068 - public function get_course_taxonomy( $id, $taxonomy ) {
1069 - $terms = get_the_terms( $id, $taxonomy );
1070 - $output = array();
1071 -
1072 - if ( $terms ) {
1073 - foreach ( $terms as $term ) {
1074 - $output[] = array(
1075 - 'id' => $term->term_id,
1076 - 'name' => $term->name,
1077 - 'slug' => $term->slug,
1078 - );
1079 - }
1080 - }
1081 -
1082 - return $output;
1083 - }
1084 -
1085 - /**
1086 - * Get Items of sections
1087 - *
1088 - * @param LP_Course $course
1089 - *
1090 - * @throws Exception
1091 - * @editor tungnx
1092 - * @modify 4.1.3
1093 - * @version 4.0.1
1094 - */
1095 - public function get_all_items( $course ): array {
1096 - $curriculum = $course->get_curriculum();
1097 - $user = learn_press_get_current_user();
1098 - $user_course = $user ? $user->get_course_data( $course->get_id() ) : false;
1099 - $output = array();
1100 -
1101 - if ( ! empty( $curriculum ) ) {
1102 - foreach ( $curriculum as $section ) {
1103 - if ( $section ) {
1104 - $data = array(
1105 - 'id' => $section->get_id(),
1106 - 'title' => $section->get_title(),
1107 - 'course_id' => $section->get_course_id(),
1108 - 'description' => $section->get_description(),
1109 - 'order' => $section->get_order(),
1110 - );
1111 -
1112 - if ( $user_course && $user->has_enrolled_or_finished( $section->get_course_id() ) ) {
1113 - $data['percent'] = $user_course->get_percent_completed_items( '', $section->get_id() );
1114 - }
1115 -
1116 - $data_item = array();
1117 -
1118 - $items = $section->get_items();
1119 -
1120 - if ( ! empty( $items ) ) {
1121 - foreach ( $items as $item ) {
1122 - $post = get_post( $item->get_id() );
1123 -
1124 - $format = array(
1125 - 'day' => __( '%s days', 'learnpress' ),
1126 - 'hour' => __( '%s hours', 'learnpress' ),
1127 - 'minute' => __( '%s mins', 'learnpress' ),
1128 - 'second' => __( '%s secs', 'learnpress' ),
1129 - );
1130 -
1131 - $user_item = $user_course ? $user_course->get_item( $item->get_id() ) : false;
1132 -
1133 - if ( $user ) {
1134 - $can_view_content_course = $user->can_view_content_course( absint( $section->get_course_id() ) );
1135 - $can_view_item = $user->can_view_item( $item->get_id(), $can_view_content_course );
1136 - }
1137 -
1138 - $data_item[] = array(
1139 - 'id' => $item->get_id(),
1140 - 'type' => $item->get_item_type(),
1141 - 'title' => $post->post_title,
1142 - 'preview' => $item->is_preview(),
1143 - 'duration' => $item->get_duration()->to_timer( $format, true ),
1144 - 'graduation' => $user_item ? $user_item->get_graduation() : '',
1145 - 'status' => $user_item ? $user_item->get_status() : '',
1146 - 'locked' => ! isset( $can_view_item->flag ) || ! $can_view_item->flag,
1147 - );
1148 - }
1149 - }
1150 -
1151 - $data['items'] = $data_item;
1152 - $output[] = $data;
1153 - }
1154 - }
1155 - }
1156 -
1157 - return $output;
1158 - }
1159 -
1160 - public function get_course_meta( $id ) {
1161 - $user_id = get_current_user_id();
1162 -
1163 - if ( ! $user_id ) {
1164 - return array();
1165 - }
1166 -
1167 - if ( ! class_exists( 'LP_Meta_Box' ) ) {
1168 - include_once LP_PLUGIN_PATH . 'inc/admin/views/meta-boxes/class-lp-meta-box.php';
1169 - }
1170 -
1171 - if ( ! class_exists( 'LP_Meta_Box_Course' ) ) {
1172 - include_once LP_PLUGIN_PATH . 'inc/admin/views/meta-boxes/course/settings.php';
1173 - }
1174 -
1175 - $metabox = new LP_Meta_Box_Course();
1176 -
1177 - $output = array();
1178 -
1179 - foreach ( $metabox->metabox( $id ) as $key => $tab ) {
1180 - if ( isset( $tab['content'] ) ) {
1181 - foreach ( $tab['content'] as $meta_key => $object ) {
1182 - if ( is_a( $object, 'LP_Meta_Box_Field' ) ) {
1183 - $object->id = $meta_key;
1184 - $output[ $meta_key ] = $object->meta_value( $id );
1185 - }
1186 - }
1187 - }
1188 - }
1189 -
1190 - return $output;
1191 - }
1192 -
1193 - protected function prepare_objects_query( $request ) {
1194 - $args = parent::prepare_objects_query( $request );
1195 -
1196 - $taxonomies = array(
1197 - 'course_category' => 'category',
1198 - 'course_tag' => 'tag',
1199 - );
1200 -
1201 - foreach ( $taxonomies as $taxonomy => $key ) {
1202 - if ( ! empty( $request[ $key ] ) ) {
1203 - $tax_query[] = array(
1204 - 'taxonomy' => $taxonomy,
1205 - 'field' => 'term_id',
1206 - 'terms' => $request[ $key ],
1207 - );
1208 - }
1209 - }
1210 -
1211 - if ( ! empty( $tax_query ) ) {
1212 - $args['tax_query'] = $tax_query;
1213 - }
1214 -
1215 - $orderby = $request->get_param( 'orderby' );
1216 - $order = $request->get_param( 'order' );
1217 -
1218 - $orderby = strtolower( is_array( $orderby ) ? (string) current( $orderby ) : (string) $orderby );
1219 - $order = strtoupper( is_array( $order ) ? (string) current( $order ) : (string) $order );
1220 -
1221 - switch ( $orderby ) {
1222 - case 'id':
1223 - $args['orderby'] = 'ID';
1224 - break;
1225 - case 'menu_order':
1226 - $args['orderby'] = 'menu_order title';
1227 - break;
1228 - case 'title':
1229 - $args['orderby'] = 'post_title';
1230 - $args['order'] = ( 'DESC' === $order ) ? 'DESC' : 'ASC';
1231 - break;
1232 - case 'relevance':
1233 - $args['orderby'] = 'relevance';
1234 - $args['order'] = 'DESC';
1235 - break;
1236 - case 'rand':
1237 - $args['orderby'] = 'rand'; // @codingStandardsIgnoreLine
1238 - break;
1239 - case 'date':
1240 - $args['orderby'] = 'date ID';
1241 - $args['order'] = ( 'ASC' === $order ) ? 'ASC' : 'DESC';
1242 - break;
1243 - case 'price':
1244 - $args['orderby'] = 'meta_value_num';
1245 - $args['meta_key'] = '_lp_price';
1246 - break;
1247 - }
1248 -
1249 - if ( is_bool( $request['on_sale'] ) ) {
1250 - $on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in';
1251 -
1252 - $filter = new LP_Course_Filter();
1253 - $filter->only_fields = array( 'ID' );
1254 -
1255 - $filter = LP_Course_DB::getInstance()->get_courses_sort_by_sale( $filter );
1256 - $on_sale_ids = LP_Course_DB::getInstance()->get_courses( $filter );
1257 - $on_sale_ids = LP_Database::get_values_by_key( $on_sale_ids );
1258 - $on_sale_ids = empty( $on_sale_ids ) ? array( 0 ) : $on_sale_ids;
1259 -
1260 - $args[ $on_sale_key ] += $on_sale_ids;
1261 - } elseif ( is_bool( $request['popular'] ) ) {
1262 - $on_popular_key = $request['popular'] ? 'post__in' : 'post__not_in';
1263 -
1264 - $filter = new LP_Course_Filter();
1265 - $filter->only_fields = array( 'ID' );
1266 - $filter->limit = $request['per_page'] ?? 10;
1267 - $filter->page = $request['page'] ?? 1;
1268 -
1269 - $filter = LP_Course_DB::getInstance()->get_courses_order_by_popular( $filter );
1270 - $on_popular_ids = LP_Course_DB::getInstance()->get_courses( $filter );
1271 - $on_popular_ids = LP_Database::get_values_by_key( $on_popular_ids );
1272 -
1273 - $on_popular_ids = empty( $on_popular_ids ) ? array( 0 ) : $on_popular_ids;
1274 -
1275 - $args[ $on_popular_key ] += $on_popular_ids;
1276 -
1277 - $args['orderby'] = 'post__in';
1278 - }
1279 -
1280 - return $args;
1281 - }
1282 -
1283 - public function get_item_schema() {
1284 - $schema = array(
1285 - '$schema' => 'http://json-schema.org/draft-04/schema#',
1286 - 'title' => $this->post_type,
1287 - 'type' => 'object',
1288 - 'properties' => array(
1289 - 'id' => array(
1290 - 'description' => __( 'A unique identifier for the resource.', 'learnpress' ),
1291 - 'type' => 'integer',
1292 - 'context' => array( 'view', 'edit' ),
1293 - 'readonly' => true,
1294 - ),
1295 - 'name' => array(
1296 - 'description' => __( 'Course name.', 'learnpress' ),
1297 - 'type' => 'string',
1298 - 'context' => array( 'view', 'edit' ),
1299 - ),
1300 - 'slug' => array(
1301 - 'description' => __( 'Course slug.', 'learnpress' ),
1302 - 'type' => 'string',
1303 - 'context' => array( 'view', 'edit' ),
1304 - ),
1305 - 'permalink' => array(
1306 - 'description' => __( 'Course URL.', 'learnpress' ),
1307 - 'type' => 'string',
1308 - 'format' => 'uri',
1309 - 'context' => array( 'view', 'edit' ),
1310 - 'readonly' => true,
1311 - ),
1312 - 'image' => array(
1313 - 'description' => __( 'Course Image URL.', 'learnpress' ),
1314 - 'type' => 'string',
1315 - 'format' => 'uri',
1316 - 'context' => array( 'view', 'edit' ),
1317 - ),
1318 - 'date_created' => array(
1319 - 'description' => __( "The date the Course was created, in the site's timezone.", 'learnpress' ),
1320 - 'type' => array( 'string', 'null' ),
1321 - 'format' => 'date-time',
1322 - 'context' => array( 'view', 'edit' ),
1323 - 'readonly' => true,
1324 - ),
1325 - 'date_created_gmt' => array(
1326 - 'description' => __( 'The date the Course was created, as GMT.', 'learnpress' ),
1327 - 'type' => array( 'string', 'null' ),
1328 - 'format' => 'date-time',
1329 - 'context' => array( 'view', 'edit' ),
1330 - 'readonly' => true,
1331 - ),
1332 - 'date_modified' => array(
1333 - 'description' => __( "The date the Course was last modified, in the site's timezone.", 'learnpress' ),
1334 - 'type' => array( 'string', 'null' ),
1335 - 'format' => 'date-time',
1336 - 'context' => array( 'view', 'edit' ),
1337 - 'readonly' => true,
1338 - ),
1339 - 'date_modified_gmt' => array(
1340 - 'description' => __( 'The date the Course was last modified, as GMT.', 'learnpress' ),
1341 - 'type' => array( 'string', 'null' ),
1342 - 'format' => 'date-time',
1343 - 'context' => array( 'view', 'edit' ),
1344 - 'readonly' => true,
1345 - ),
1346 - 'on_sale' => array(
1347 - 'description' => __( 'Display courses if they are on sale.', 'learnpress' ),
1348 - 'type' => 'boolean',
1349 - 'context' => array( 'view', 'edit' ),
1350 - 'readonly' => true,
1351 - ),
1352 - 'status' => array(
1353 - 'description' => __( 'Course status (post status).', 'learnpress' ),
1354 - 'type' => 'string',
1355 - 'default' => 'publish',
1356 - 'enum' => array_merge( array_keys( get_post_statuses() ), array( 'future' ) ),
1357 - 'context' => array( 'view', 'edit' ),
1358 - ),
1359 - 'content' => array(
1360 - 'description' => __( 'Course content.', 'learnpress' ),
1361 - 'type' => 'string',
1362 - 'context' => array( 'view', 'edit' ),
1363 - ),
1364 - 'excerpt' => array(
1365 - 'description' => __( 'Retrieves the course excerpt..', 'learnpress' ),
1366 - 'type' => 'string',
1367 - 'context' => array( 'view', 'edit' ),
1368 - ),
1369 - 'duration' => array(
1370 - 'description' => __( 'Duration', 'learnpress' ),
1371 - 'type' => 'string',
1372 - 'context' => array( 'view' ),
1373 - ),
1374 - 'count_students' => array(
1375 - 'description' => __( 'Count the number of enrolled students.', 'learnpress' ),
1376 - 'type' => 'integer',
1377 - 'context' => array( 'view' ),
1378 - 'readonly' => true,
1379 - ),
1380 - 'can_finish' => array(
1381 - 'description' => __( 'Can finish the course', 'learnpress' ),
1382 - 'type' => 'boolean',
1383 - 'context' => array( 'view' ),
1384 - 'readonly' => true,
1385 - ),
1386 - 'can_retake' => array(
1387 - 'description' => __( 'Can retake the course', 'learnpress' ),
1388 - 'type' => 'boolean',
1389 - 'context' => array( 'view' ),
1390 - 'readonly' => true,
1391 - ),
1392 - 'ratake_count' => array(
1393 - 'description' => __( 'Total retakes', 'learnpress' ),
1394 - 'type' => 'integer',
1395 - 'context' => array( 'view' ),
1396 - 'readonly' => true,
1397 - ),
1398 - 'rataken' => array(
1399 - 'description' => __( 'Retaken', 'learnpress' ),
1400 - 'type' => 'integer',
1401 - 'context' => array( 'view' ),
1402 - 'readonly' => true,
1403 - ),
1404 - 'rating' => array(
1405 - 'description' => __( 'Course Review add-on', 'learnpress' ),
1406 - 'type' => array( 'boolean', 'integer' ),
1407 - 'context' => array( 'view' ),
1408 - 'readonly' => true,
1409 - ),
1410 - 'price' => array(
1411 - 'description' => __( 'Course Price', 'learnpress' ),
1412 - 'type' => 'integer',
1413 - 'context' => array( 'view' ),
1414 - 'readonly' => true,
1415 - ),
1416 - 'price_rendered' => array(
1417 - 'description' => __( 'Course Price Rendered', 'learnpress' ),
1418 - 'type' => 'string',
1419 - 'context' => array( 'view' ),
1420 - 'readonly' => true,
1421 - ),
1422 - 'origin_price' => array(
1423 - 'description' => __( 'Course Origin Price', 'learnpress' ),
1424 - 'type' => 'integer',
1425 - 'context' => array( 'view' ),
1426 - 'readonly' => true,
1427 - ),
1428 - 'origin_price_rendered' => array(
1429 - 'description' => __( 'Course Origin Price Rendered', 'learnpress' ),
1430 - 'type' => 'string',
1431 - 'context' => array( 'view' ),
1432 - 'readonly' => true,
1433 - ),
1434 - 'sale_price' => array(
1435 - 'description' => __( 'Course Sale Price', 'learnpress' ),
1436 - 'type' => 'integer',
1437 - 'context' => array( 'view' ),
1438 - 'readonly' => true,
1439 - ),
1440 - 'sale_price_rendered' => array(
1441 - 'description' => __( 'Course Sale Price Rendered', 'learnpress' ),
1442 - 'type' => 'string',
1443 - 'context' => array( 'view' ),
1444 - 'readonly' => true,
1445 - ),
1446 - 'categories' => array(
1447 - 'description' => __( 'List of categories.', 'learnpress' ),
1448 - 'type' => 'array',
1449 - 'context' => array( 'view', 'edit' ),
1450 - 'items' => array(
1451 - 'type' => 'object',
1452 - 'properties' => array(
1453 - 'id' => array(
1454 - 'description' => __( 'Category ID.', 'learnpress' ),
1455 - 'type' => 'integer',
1456 - 'context' => array( 'view', 'edit' ),
1457 - ),
1458 - 'name' => array(
1459 - 'description' => __( 'Category name.', 'learnpress' ),
1460 - 'type' => 'string',
1461 - 'context' => array( 'view', 'edit' ),
1462 - 'readonly' => true,
1463 - ),
1464 - 'slug' => array(
1465 - 'description' => __( 'Category slug.', 'learnpress' ),
1466 - 'type' => 'string',
1467 - 'context' => array( 'view', 'edit' ),
1468 - 'readonly' => true,
1469 - ),
1470 - ),
1471 - ),
1472 - ),
1473 - 'tags' => array(
1474 - 'description' => __( 'List of tags.', 'learnpress' ),
1475 - 'type' => 'array',
1476 - 'context' => array( 'view', 'edit' ),
1477 - 'items' => array(
1478 - 'type' => 'object',
1479 - 'properties' => array(
1480 - 'id' => array(
1481 - 'description' => __( 'Tag ID.', 'learnpress' ),
1482 - 'type' => 'integer',
1483 - 'context' => array( 'view', 'edit' ),
1484 - ),
1485 - 'name' => array(
1486 - 'description' => __( 'Tag name.', 'learnpress' ),
1487 - 'type' => 'string',
1488 - 'context' => array( 'view', 'edit' ),
1489 - 'readonly' => true,
1490 - ),
1491 - 'slug' => array(
1492 - 'description' => __( 'Tag slug.', 'learnpress' ),
1493 - 'type' => 'string',
1494 - 'context' => array( 'view', 'edit' ),
1495 - 'readonly' => true,
1496 - ),
1497 - ),
1498 - ),
1499 - ),
1500 - 'instructor' => array(
1501 - 'description' => __( 'Retrieves the course sections and items..', 'learnpress' ),
1502 - 'type' => 'array',
1503 - 'context' => array( 'view', 'edit' ),
1504 - 'items' => array(
1505 - 'type' => 'object',
1506 - 'properties' => array(
1507 - 'id' => array(
1508 - 'description' => __( 'User ID.', 'learnpress' ),
1509 - 'type' => 'integer',
1510 - 'context' => array( 'view' ),
1511 - ),
1512 - 'name' => array(
1513 - 'description' => __( 'Display name.', 'learnpress' ),
1514 - 'type' => 'string',
1515 - 'context' => array( 'view' ),
1516 - 'readonly' => true,
1517 - ),
1518 - 'description' => array(
1519 - 'description' => __( 'Tag slug.', 'learnpress' ),
1520 - 'type' => 'string',
1521 - 'context' => array( 'view' ),
1522 - 'readonly' => true,
1523 - ),
1524 - 'social' => array(
1525 - 'description' => __( 'Social Infor.', 'learnpress' ),
1526 - 'type' => 'array',
1527 - 'context' => array( 'view' ),
1528 - 'readonly' => true,
1529 - ),
1530 - ),
1531 - ),
1532 - ),
1533 - 'sections' => array(
1534 - 'description' => __( 'Retrieves the course sections and items..', 'learnpress' ),
1535 - 'type' => 'array',
1536 - 'context' => array( 'view', 'edit' ),
1537 - 'items' => array(
1538 - 'type' => 'object',
1539 - 'properties' => array(
1540 - 'id' => array(
1541 - 'description' => __( 'Section ID.', 'learnpress' ),
1542 - 'type' => 'integer',
1543 - 'context' => array( 'view', 'edit' ),
1544 - ),
1545 - 'title' => array(
1546 - 'description' => __( 'Section name.', 'learnpress' ),
1547 - 'type' => 'string',
1548 - 'context' => array( 'view', 'edit' ),
1549 - ),
1550 - 'course_id' => array(
1551 - 'description' => __( 'Course ID.', 'learnpress' ),
1552 - 'type' => 'integer',
1553 - 'context' => array( 'view', 'edit' ),
1554 - ),
1555 - 'description' => array(
1556 - 'description' => __( 'Section description.', 'learnpress' ),
1557 - 'type' => 'string',
1558 - 'context' => array( 'view', 'edit' ),
1559 - ),
1560 - 'items' => array(
1561 - 'description' => __( 'Section items.', 'learnpress' ),
1562 - 'type' => 'array',
1563 - 'context' => array( 'view', 'edit' ),
1564 - 'items' => array(
1565 - 'id' => array(
1566 - 'description' => __( 'Item ID.', 'learnpress' ),
1567 - 'type' => 'integer',
1568 - 'context' => array( 'view', 'edit' ),
1569 - ),
1570 - 'type' => array(
1571 - 'description' => __( 'Item Type.', 'learnpress' ),
1572 - 'type' => 'string',
1573 - 'context' => array( 'view', 'edit' ),
1574 - ),
1575 - 'title' => array(
1576 - 'description' => __( 'Item title.', 'learnpress' ),
1577 - 'type' => 'string',
1578 - 'context' => array( 'view', 'edit' ),
1579 - ),
1580 - 'preview' => array(
1581 - 'description' => __( 'Item ID.', 'learnpress' ),
1582 - 'type' => 'boolean',
1583 - 'context' => array( 'view', 'edit' ),
1584 - ),
1585 - 'percent' => array(
1586 - 'description' => __( 'Percent.', 'learnpress' ),
1587 - 'type' => 'integer',
1588 - 'context' => array( 'view', 'edit' ),
1589 - ),
1590 - 'duration' => array(
1591 - 'description' => __( 'Duration.', 'learnpress' ),
1592 - 'type' => 'string',
1593 - 'context' => array( 'view', 'edit' ),
1594 - ),
1595 - 'graduation' => array(
1596 - 'description' => __( 'Graduation.', 'learnpress' ),
1597 - 'type' => 'string',
1598 - 'context' => array( 'view', 'edit' ),
1599 - ),
1600 - 'status' => array(
1601 - 'description' => __( 'Status.', 'learnpress' ),
1602 - 'type' => 'string',
1603 - 'context' => array( 'view', 'edit' ),
1604 - ),
1605 - 'locked' => array(
1606 - 'description' => __( 'Locked.', 'learnpress' ),
1607 - 'type' => 'boolean',
1608 - 'context' => array( 'view', 'edit' ),
1609 - ),
1610 - ),
1611 - ),
1612 - ),
1613 - ),
1614 - ),
1615 - 'course_data' => array(
1616 - 'description' => __( 'List of course user data.', 'learnpress' ),
1617 - 'type' => 'array',
1618 - 'context' => array( 'view' ),
1619 - 'items' => array(
1620 - 'type' => 'object',
1621 - 'properties' => array(
1622 - 'graduation' => array(
1623 - 'description' => __( 'Graduation', 'learnpress' ),
1624 - 'type' => 'string',
1625 - 'context' => array( 'view' ),
1626 - ),
1627 - 'status' => array(
1628 - 'description' => __( 'Status', 'learnpress' ),
1629 - 'type' => 'string',
1630 - 'context' => array( 'view' ),
1631 - 'readonly' => true,
1632 - ),
1633 - 'start_time' => array(
1634 - 'description' => __( 'Start time', 'learnpress' ),
1635 - 'type' => 'string',
1636 - 'context' => array( 'view' ),
1637 - 'readonly' => true,
1638 - ),
1639 - 'end_time' => array(
1640 - 'description' => __( 'End time', 'learnpress' ),
1641 - 'type' => 'string',
1642 - 'context' => array( 'view' ),
1643 - 'readonly' => true,
1644 - ),
1645 - 'expiration_time' => array(
1646 - 'description' => __( 'Expiration time', 'learnpress' ),
1647 - 'type' => 'string',
1648 - 'context' => array( 'view' ),
1649 - 'readonly' => true,
1650 - ),
1651 - ),
1652 - ),
1653 - ),
1654 - ),
1655 - );
1656 -
1657 - return $this->add_additional_fields_schema( $schema );
1658 - }
1659 -
1660 - public function get_collection_params() {
1661 - $params = parent::get_collection_params();
1662 -
1663 - $params['orderby']['enum'] = array_merge( $params['orderby']['enum'], array( 'menu_order', 'price' ) );
1664 -
1665 - $params['category'] = array(
1666 - 'description' => 'Limit the result set to courses assigned to a specific category ID.',
1667 - 'type' => 'string',
1668 - 'sanitize_callback' => 'wp_parse_id_list',
1669 - 'validate_callback' => 'rest_validate_request_arg',
1670 - );
1671 - $params['tag'] = array(
1672 - 'description' => 'Limit the result set to courses assigned to a specific tag ID.',
1673 - 'type' => 'string',
1674 - 'sanitize_callback' => 'wp_parse_id_list',
1675 - 'validate_callback' => 'rest_validate_request_arg',
1676 - );
1677 - $params['course_filter'] = array(
1678 - 'description' => 'Filter by course to in-progress, passed, failed.',
1679 - 'type' => 'string',
1680 - 'sanitize_callback' => 'sanitize_text_field',
1681 - 'validate_callback' => 'rest_validate_request_arg',
1682 - );
1683 -
1684 - $params['on_sale'] = array(
1685 - 'description' => 'Get item learned by user.',
1686 - 'type' => 'boolean',
1687 - 'validate_callback' => 'rest_validate_request_arg',
1688 - );
1689 -
1690 - $params['popular'] = array(
1691 - 'description' => 'Get item popularity.',
1692 - 'type' => 'boolean',
1693 - 'validate_callback' => 'rest_validate_request_arg',
1694 - );
1695 - $params['optimize'] = array(
1696 - 'description' => 'Disable some fields in the schema.',
1697 - 'type' => array( 'boolean', 'string' ),
1698 - 'validate_callback' => 'wp_parse_id_list',
1699 - );
1700 -
1701 - return $params;
1702 - }
1703 -}
1 +<?php
2 +
3 +use LearnPress\Models\CourseModel;
4 +use LearnPress\Models\CoursePostModel;
5 +use LearnPress\Models\Courses;
6 +use LearnPress\Models\PostModel;
7 +use LearnPress\Models\UserItems\UserCourseModel;
8 +use LearnPress\Models\UserModel;
9 +
10 +class LP_Jwt_Courses_V1_Controller extends LP_REST_Jwt_Posts_Controller {
11 + protected $namespace = 'learnpress/v1';
12 +
13 + protected $rest_base = 'courses';
14 +
15 + protected $post_type = LP_COURSE_CPT;
16 +
17 + protected $hierarchical = true;
18 +
19 + public function register_routes() {
20 + register_rest_route(
21 + $this->namespace,
22 + '/' . $this->rest_base,
23 + array(
24 + array(
25 + 'methods' => WP_REST_Server::READABLE,
26 + 'callback' => array( $this, 'get_courses' ),
27 + 'permission_callback' => array( $this, 'get_items_permissions_check' ),
28 + 'args' => $this->get_collection_params(),
29 + ),
30 + array(
31 + 'methods' => WP_REST_Server::CREATABLE,
32 + 'callback' => array( $this, 'create_item' ),
33 + 'permission_callback' => array( $this, 'create_item_permissions_check' ),
34 + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
35 + ),
36 + 'schema' => array( $this, 'get_public_item_schema' ),
37 + )
38 + );
39 +
40 + register_rest_route(
41 + $this->namespace,
42 + '/' . $this->rest_base . '/(?P<id>[\d]+)',
43 + array(
44 + 'args' => array(
45 + 'id' => array(
46 + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
47 + 'type' => 'integer',
48 + ),
49 + ),
50 + array(
51 + 'methods' => WP_REST_Server::READABLE,
52 + 'callback' => array( $this, 'get_item' ),
53 + 'permission_callback' => array( $this, 'get_item_permissions_check' ),
54 + 'args' => array(
55 + 'context' => $this->get_context_param(
56 + array(
57 + 'default' => 'view',
58 + )
59 + ),
60 + ),
61 + ),
62 + 'schema' => array( $this, 'get_public_item_schema' ),
63 + )
64 + );
65 +
66 + register_rest_route(
67 + $this->namespace,
68 + '/' . $this->rest_base . '/enroll',
69 + array(
70 + 'args' => array(
71 + 'id' => array(
72 + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
73 + 'type' => 'integer',
74 + ),
75 + ),
76 + array(
77 + 'methods' => WP_REST_Server::CREATABLE,
78 + 'callback' => array( $this, 'enroll_course' ),
79 + 'permission_callback' => array( $this, 'get_course_need_login_check' ),
80 + 'args' => array(
81 + 'context' => $this->get_context_param(
82 + array(
83 + 'default' => 'edit',
84 + )
85 + ),
86 + ),
87 + ),
88 + )
89 + );
90 +
91 + register_rest_route(
92 + $this->namespace,
93 + '/' . $this->rest_base . '/finish',
94 + array(
95 + 'args' => array(
96 + 'id' => array(
97 + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
98 + 'type' => 'integer',
99 + ),
100 + ),
101 + array(
102 + 'methods' => WP_REST_Server::CREATABLE,
103 + 'callback' => array( $this, 'finish_course' ),
104 + 'permission_callback' => '__return_true',
105 + 'args' => array(
106 + 'context' => $this->get_context_param(
107 + array(
108 + 'default' => 'edit',
109 + )
110 + ),
111 + ),
112 + ),
113 + )
114 + );
115 +
116 + register_rest_route(
117 + $this->namespace,
118 + '/' . $this->rest_base . '/retake',
119 + array(
120 + 'args' => array(
121 + 'id' => array(
122 + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
123 + 'type' => 'integer',
124 + ),
125 + ),
126 + array(
127 + 'methods' => WP_REST_Server::CREATABLE,
128 + 'callback' => array( $this, 'retake_course' ),
129 + 'permission_callback' => '__return_true',
130 + 'args' => array(
131 + 'context' => $this->get_context_param(
132 + array(
133 + 'default' => 'edit',
134 + )
135 + ),
136 + ),
137 + ),
138 + )
139 + );
140 +
141 + register_rest_route(
142 + $this->namespace,
143 + '/' . $this->rest_base . '/verify-receipt',
144 + array(
145 + 'methods' => WP_REST_Server::CREATABLE,
146 + 'callback' => array( $this, 'verify_receipt' ),
147 + 'permission_callback' => '__return_true',
148 + 'args' => array(
149 + 'context' => $this->get_context_param(
150 + array(
151 + 'default' => 'edit',
152 + )
153 + ),
154 + 'receipt-data' => array(
155 + 'description' => esc_html__( 'Receipt data.', 'learnpress' ),
156 + 'type' => 'string',
157 + ),
158 + ),
159 + )
160 + );
161 + }
162 +
163 + public function get_items_permissions_check( $request ) {
164 + $post_type = get_post_type_object( $this->post_type );
165 +
166 + if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
167 + return new WP_Error(
168 + 'rest_forbidden_context',
169 + __( 'Sorry, you are not allowed to edit posts in this post type.' ),
170 + array( 'status' => rest_authorization_required_code() )
171 + );
172 + }
173 +
174 + return true;
175 + }
176 +
177 + public function get_course_need_login_check( $request ) {
178 + if ( ! is_user_logged_in() ) {
179 + return new WP_Error(
180 + 'rest_forbidden_context',
181 + __( 'Please login to continue' ),
182 + array( 'status' => 401 )
183 + );
184 + }
185 +
186 + return true;
187 + }
188 +
189 + /**
190 + * Checks if a course can be read.
191 + *
192 + * Correctly handles courses with the inherit status.
193 + *
194 + * @return bool Whether the post can be read.
195 + * *@author Nhamdv
196 + *
197 + */
198 + public function check_read_permission( $post_id ) {
199 + if ( empty( absint( $post_id ) ) ) {
200 + return false;
201 + }
202 +
203 + $post = get_post( $post_id );
204 +
205 + if ( ! $post ) {
206 + return false;
207 + }
208 +
209 + // Is the post readable?
210 + if ( 'publish' === $post->post_status || current_user_can( 'read_post', $post->ID ) ) {
211 + return true;
212 + }
213 +
214 + $post_status_obj = get_post_status_object( $post->post_status );
215 + if ( $post_status_obj && $post_status_obj->public ) {
216 + return true;
217 + }
218 +
219 + // Can we read the parent if we're inheriting?
220 + if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) {
221 + $parent = get_post( $post->post_parent );
222 +
223 + if ( $parent ) {
224 + return $this->check_read_permission( $parent );
225 + }
226 + }
227 +
228 + /*
229 + * If there isn't a parent, but the status is set to inherit, assume
230 + * it's published (as per get_post_status()).
231 + */
232 + if ( 'inherit' === $post->post_status ) {
233 + return true;
234 + }
235 +
236 + return false;
237 + }
238 +
239 + protected function get_object( $course = 0 ) {
240 + global $post;
241 +
242 + if ( false === $course && isset( $post, $post->ID ) && LP_COURSE_CPT === get_post_type( $post->ID ) ) {
243 + $id = absint( $post->ID );
244 + } elseif ( is_numeric( $course ) ) {
245 + $id = $course;
246 + } elseif ( $course instanceof LP_Course ) {
247 + $id = $course->get_id();
248 + } elseif ( ! empty( $course->ID ) ) {
249 + $id = $course->ID;
250 + }
251 +
252 + return learn_press_get_course( $id );
253 + }
254 +
255 + public function verify_receipt( $request ) {
256 + $response = new LP_REST_Response();
257 + $receipt = ! empty( $request['receipt-data'] ) ? $request['receipt-data'] : '';
258 + $is_ios = ! empty( $request['is-ios'] ) ? true : false;
259 +
260 + // Get request header.
261 + $platform = ! empty( $request->get_header( 'x-platform' ) ) ? sanitize_text_field( $request->get_header( 'x-platform' ) ) : '';
262 + $password = function_exists( 'learnpress_mobile_iap_settings' ) ? learnpress_mobile_iap_settings( $platform )['apple_token_secret'] : LP_Settings::instance()->get( 'in_app_purchase_apple_shared_secret', '' );
263 +
264 + try {
265 + if ( empty( $receipt ) ) {
266 + throw new Exception( __( 'Receipt data is empty.', 'learnpress' ) );
267 + }
268 +
269 + if ( $is_ios ) {
270 + $course_id = ! empty( $request['course-id'] ) ? absint( $request['course-id'] ) : 0;
271 +
272 + if ( empty( $password ) ) {
273 + throw new Exception( __( 'The secret key is empty.', 'learnpress' ) );
274 + }
275 +
276 + $url = LP_Settings::instance()->get( 'in_app_purchase_apple_sandbox' ) === 'yes' ? 'https://sandbox.itunes.apple.com/verifyReceipt' : 'https://buy.itunes.apple.com/verifyReceipt';
277 +
278 + $verify = wp_remote_post(
279 + $url,
280 + array(
281 + 'method' => 'POST',
282 + 'timeout' => 60,
283 + 'body' => wp_json_encode(
284 + array(
285 + 'receipt-data' => $receipt,
286 + 'password' => $password,
287 + )
288 + ),
289 + )
290 + );
291 +
292 + if ( is_wp_error( $verify ) ) {
293 + throw new Exception( $verify->get_error_message() );
294 + }
295 +
296 + $body = json_decode( wp_remote_retrieve_body( $verify ) );
297 +
298 + if ( $body->status !== 0 ) {
299 + throw new Exception( __( 'Cannot verify the receipt', 'learnpress' ) );
300 + }
301 +
302 + $latest_receipt_info = ! empty( $body->latest_receipt_info ) ? $body->latest_receipt_info : array();
303 +
304 + if ( empty( $latest_receipt_info ) ) {
305 + throw new Exception( __( 'The course ID is invalid.', 'learnpress' ) );
306 + }
307 +
308 + $course_ids = array_map(
309 + function ( $receipt_id ) {
310 + return absint( $receipt_id->product_id );
311 + },
312 + $latest_receipt_info
313 + );
314 +
315 + if ( ! in_array( $course_id, $course_ids ) ) {
316 + throw new Exception( __( 'The course ID is invalid.', 'learnpress' ) );
317 + }
318 + } else {
319 + $receipt = json_decode( $receipt, true );
320 + $package_name = $receipt['packageName'] ?? '';
321 + $course_id = ! empty( $receipt['productId'] ) ? absint( $receipt['productId'] ) : 0;
322 + $purchase_token = $receipt['purchaseToken'] ?? '';
323 +
324 + if ( ! function_exists( 'learnpress_in_app_purchase_get_access_token' ) ) {
325 + throw new Exception( __( 'Cannot verify the receipt', 'learnpress' ) );
326 + }
327 +
328 + $access_token = function_exists( 'learnpress_mobile_iap_settings' ) ? learnpress_in_app_purchase_get_access_token( $platform ) : learnpress_in_app_purchase_get_access_token();
329 +
330 + $verify = wp_remote_get(
331 + 'https://androidpublisher.googleapis.com/androidpublisher/v3/applications/' . $package_name . '/purchases/products/' . $course_id . '/tokens/' . $purchase_token,
332 + array(
333 + 'headers' => array(
334 + 'Content-Type' => 'application/json',
335 + 'Authorization' => 'Bearer ' . $access_token,
336 + ),
337 + 'timeout' => 60,
338 + )
339 + );
340 +
341 + $body = json_decode( wp_remote_retrieve_body( $verify ) );
342 +
343 + if ( isset( $body->error->message ) ) {
344 + throw new Exception( $body->error->message );
345 + }
346 + }
347 +
348 + $course = learn_press_get_course( $course_id );
349 +
350 + if ( ! $course ) {
351 + throw new Exception( __( 'The course ID is invalid.', 'learnpress' ) );
352 + }
353 + $user = learn_press_get_current_user();
354 +
355 + $cart = LearnPress::instance()->cart;
356 + $checkout = LP_Checkout::instance();
357 +
358 + if ( ! learn_press_enable_cart() ) {
359 + // $order_awaiting_payment = LearnPress::instance()->session->get( 'order_awaiting_payment' );
360 + $cart->empty_cart();
361 + // LearnPress::instance()->session->set( 'order_awaiting_payment', $order_awaiting_payment );
362 + }
363 +
364 + $cart_id = $cart->add_to_cart( $course_id, 1, array() );
365 +
366 + if ( ! $cart_id ) {
367 + throw new Exception( esc_html__( 'Error: The course cannot be added to the cart.', 'learnpress' ) );
368 + }
369 +
370 + if ( is_user_logged_in() ) {
371 + $order_id = $checkout->create_order();
372 + $order = new LP_Order( $order_id );
373 + $order->payment_complete();
374 + $cart->empty_cart();
375 + }
376 +
377 + $response->status = 'success';
378 + $response->message = esc_html__( 'Verify Receipt Data successfully.', 'learnpress' );
379 + } catch ( \Throwable $th ) {
380 + error_log( $th->getMessage() );
381 + $response->status = 'error';
382 + $response->message = $th->getMessage();
383 + }
384 +
385 + return rest_ensure_response( $response );
386 + }
387 +
388 + /**
389 + * Convert params App to query courses.
390 + *
391 + * @param $params
392 + *
393 + * @return array|mixed
394 + */
395 + public function convert_params_query_courses( $params = [] ) {
396 + $params['limit'] = $params['per_page'] ?? 10;
397 +
398 + $params['paged'] = $params['page'] ?? 1;
399 +
400 + if ( ! empty( $params['popular'] ) ) {
401 + $params['order_by'] = 'popular';
402 + } else {
403 + $params['order_by'] = $params['orderby'] ?? 'menu_order';
404 + switch ( $params['order_by'] ) {
405 + case 'date':
406 + $params['order_by'] = 'post_date';
407 + $params['order'] = $params['order'] ?? 'DESC';
408 + break;
409 + case 'title':
410 + $params['order_by'] = 'post_title';
411 + break;
412 + case 'price':
413 + if ( ! empty( $params['order'] ) && 'asc' === $params['order'] ) {
414 + $params['order_by'] = 'price_low';
415 + }
416 + break;
417 + default:
418 + $params['order'] = $params['order'] ?? 'DESC';
419 + break;
420 + }
421 + }
422 +
423 + if ( ! empty( $params['search'] ) ) {
424 + $params['c_search'] = trim( $params['search'] );
425 + }
426 +
427 + if ( ! empty( $params['user'] ) ) {
428 + $params['c_author'] = $params['user'];
429 + }
430 +
431 + if ( ! empty( $params['on_sale'] ) ) {
432 + $params['sort_by'] = 'on_sale';
433 + }
434 +
435 + $params['return_type'] = 'json';
436 + //$params['c_only_fields'] = empty( $params['c_only_fields'] ) ? '' : explode( ',', $params['c_only_fields'] );
437 + $params['term_id'] = empty( $params['category'] ) || 'all' === $params['category'] ? '' : implode( ',', $params['category'] );
438 +
439 + return $params;
440 + }
441 +
442 + /**
443 + * Get courses.
444 + *
445 + * @param WP_REST_Request $request
446 + *
447 + * @return WP_REST_Response|LP_REST_Response
448 + * @since 4.2.6.9
449 + * @version 1.0.2
450 + */
451 + public function get_courses( WP_REST_Request $request ) {
452 + $res = new LP_REST_Response();
453 + $courses = [];
454 + $total = 0;
455 + $total_pages = 0;
456 + try {
457 + $filter = new LP_Course_Filter();
458 + $params = $request->get_params();
459 + $params = $this->convert_params_query_courses( $params );
460 +
461 + Courses::handle_params_for_query_courses( $filter, $params );
462 + $filter->post_status = [ PostModel::STATUS_PUBLISH ];
463 +
464 + // Filter by slug (post_name).
465 + if ( ! empty( $params['slug'] ) ) {
466 + $filter->post_name = is_array( $params['slug'] ) ? reset( $params['slug'] ) : $params['slug'];
467 + }
468 +
469 + $key_cache = 'api/' . md5( json_encode( $params ) );
470 + $key_cache_total = $key_cache . '_total';
471 + $key_cache_total_pages = $key_cache . '_total_pages';
472 + $lp_courses_cache = new LP_Courses_Cache( true );
473 +
474 + if ( ! empty( $params['learned'] ) ) {
475 + $user_id = get_current_user_id();
476 + // For tst user hard code
477 + // $user_id = ! empty( $params['user_id'] ) ? $params['user_id'] : get_current_user_id();
478 + if ( $user_id <= 0 ) {
479 + $user_id = - 1;
480 + }
481 + // Get courses that user has learned
482 + /*$lp_user_courses = LP_User_Items_DB::getInstance();
483 + $filter_user_course = new LP_User_Items_Filter();
484 + $filter_user_course->only_fields = array( 'DISTINCT(item_id) AS ID' );
485 + $filter_user_course->user_id = $user_id;
486 + $filter_user_course->item_type = LP_COURSE_CPT;
487 + $filter_user_course->return_string_query = true;
488 + $user_courses_query = $lp_user_courses->get_user_items( $filter_user_course );
489 + $params = $lp_user_courses->get_user_items( $filter_user_course );
490 + $filter->where[] = "AND p.ID IN ({$user_courses_query})";*/
491 +
492 + $lp_user_items_db = LP_User_Items_DB::getInstance();
493 + $filter->fields[] = 'ui.user_item_id, ui.user_id, ui.item_id, ui.start_time, ui.end_time, ui.item_type, ui.graduation, ui.status';
494 + $filter->join[] = "LEFT JOIN $lp_user_items_db->tb_lp_user_items AS ui ON p.ID = ui.item_id";
495 + $filter->where[] = $lp_user_items_db->wpdb->prepare( 'AND ui.user_id = %s', $user_id );
496 + $filter->where[] = $lp_user_items_db->wpdb->prepare( 'AND ui.item_type = %s', LP_COURSE_CPT );
497 + if ( ! empty( $params['course_filter'] ) ) {
498 + $filter->where[] = $lp_user_items_db->wpdb->prepare( 'AND ui.graduation = %s', $params['course_filter'] );
499 + }
500 + } else {
501 + // Check cache with case not learned
502 + $courses_cache = $lp_courses_cache->get_cache( $key_cache );
503 + if ( $courses_cache !== false ) {
504 + $courses = $courses_cache;
505 + $total = (int) $lp_courses_cache->get_cache( $key_cache_total );
506 + $total_pages = (int) $lp_courses_cache->get_cache( $key_cache_total_pages );
507 +
508 + $response = rest_ensure_response( $courses );
509 + $response->header( 'X-WP-Total', $total );
510 + $response->header( 'X-WP-TotalPages', $total_pages );
511 +
512 + return $response;
513 + }
514 + }
515 +
516 + $rs_courses = Courses::get_courses( $filter, $total );
517 + $courses = $this->prepare_struct_courses_response( $rs_courses, $params );
518 + $total_pages = LP_Database::get_total_pages( $filter->limit, $total );
519 + } catch ( Throwable $e ) {
520 + $res->message = $e->getMessage();
521 +
522 + return $res;
523 + }
524 +
525 + $response = rest_ensure_response( $courses );
526 + $response->header( 'X-WP-Total', $total );
527 + $response->header( 'X-WP-TotalPages', $total_pages );
528 +
529 + // Set cache with case not learned
530 + if ( empty( $params['learned'] ) ) {
531 + $lp_courses_cache->set_cache( $key_cache, json_encode( $courses, JSON_UNESCAPED_UNICODE ) );
532 + $lp_courses_cache->save_cache_keys( LP_Courses_Cache::KEYS_QUERY_COURSES_APP, $key_cache );
533 + $lp_courses_cache->set_cache( $key_cache_total, $total );
534 + $lp_courses_cache->save_cache_keys( LP_Courses_Cache::KEYS_QUERY_COURSES_APP, $key_cache_total );
535 + $lp_courses_cache->set_cache( $key_cache_total_pages, $total_pages );
536 + $lp_courses_cache->save_cache_keys( LP_Courses_Cache::KEYS_QUERY_COURSES_APP, $key_cache_total_pages );
537 + }
538 +
539 + return $response;
540 + }
541 +
542 + /**
543 + * @throws Exception
544 + */
545 + public function prepare_struct_courses_response( $courses, $params ): array {
546 + $data = [];
547 + foreach ( $courses as $courseObj ) {
548 + $course = CourseModel::find( $courseObj->ID, true );
549 + if ( empty( $course ) ) {
550 + continue;
551 + }
552 +
553 + $courseObjPrepare = new stdClass();
554 + $courseObjPrepare->id = (int) $courseObj->ID ?? 0;
555 + $courseObjPrepare->name = html_entity_decode( $course->get_title() );
556 + $courseObjPrepare->slug = $courseObj->post_name ?? get_post_field( 'post_name', $courseObj->ID );
557 + $courseObjPrepare->permalink = get_permalink( $courseObj->ID );
558 + $courseObjPrepare->excerpt = $courseObj->post_excerpt ?? '';
559 + $courseObjPrepare->count_students = method_exists( $course, 'count_students' ) ? (int) $course->count_students() : 0;
560 + $courseObjPrepare->image = $course->get_image_url();
561 + $author = $course->get_author_model();
562 + $courseObjPrepare->instructor = ! empty( $author ) ? $this->get_author_info( $author ) : [];
563 + $duration = $course->get_meta_value_by_key( CoursePostModel::META_KEY_DURATION, '' );
564 + $duration_arr = explode( ' ', $duration );
565 + $duration_number = floatval( $duration_arr[0] ?? 0 );
566 + $duration_type = $duration_arr[1] ?? '';
567 + if ( empty( $duration_number ) ) {
568 + $duration_str = __( 'Lifetime', 'learnpress' );
569 + } else {
570 + $duration_str = LP_Datetime::get_string_plural_duration( $duration_number, $duration_type );
571 + }
572 + $courseObjPrepare->duration = $duration_str;
573 + $course_categories = $course->get_categories();
574 + // Add key id, for old app.
575 + foreach ( $course_categories as $k => $category ) {
576 + $category->id = $category->term_id;
577 + $course_categories[ $k ] = $category;
578 + }
579 + $courseObjPrepare->categories = $course_categories;
580 + $courseObjPrepare->price = $course->get_price();
581 + $courseObjPrepare->price_rendered = $this->render_course_price( $course );
582 + $courseObjPrepare->origin_price = $course->get_regular_price();
583 + $courseObjPrepare->origin_price_rendered = html_entity_decode(
584 + learn_press_format_price( $courseObjPrepare->origin_price, true )
585 + );
586 + $courseObjPrepare->on_sale = $course->has_sale_price();
587 + $courseObjPrepare->sale_price = (float) $course->get_sale_price();
588 + $courseObjPrepare->sale_price_rendered = html_entity_decode(
589 + learn_press_format_price( $course->get_sale_price(), true )
590 + );
591 + // When release Addon Course Review v4.1.3 a long time, we will remove this code.
592 + $courseObjPrepare->rating = $this->get_course_rating( $courseObj->ID );
593 + // All settings of the course metabox, same as the single course response.
594 + $courseObjPrepare->meta_data = (object) $this->get_course_meta( $courseObj->ID );
595 +
596 + // Add more fields
597 + if ( ! empty( $params['learned'] ) ) {
598 + $courseObjPrepare->course_data = new stdClass();
599 + $courseObjPrepare->course_data->user_item_id = $courseObj->user_item_id ?? 0;
600 + $courseObjPrepare->course_data->user_id = $courseObj->user_id ?? 0;
601 + $courseObjPrepare->course_data->item_id = $courseObj->item_id ?? 0;
602 + $courseObjPrepare->course_data->item_type = $courseObj->item_type ?? 0;
603 + $courseObjPrepare->course_data->status = $courseObj->status ?? '';
604 + $courseObjPrepare->course_data->graduation = $courseObj->graduation ?? '';
605 + $courseObjPrepare->course_data->start_time = $courseObj->start_time ?? '';
606 + $courseObjPrepare->course_data->end_time = $courseObj->end_time ?? '';
607 +
608 + $userCourse = new UserCourseModel( $courseObjPrepare->course_data );
609 + $expirationTime = $userCourse->get_expiration_time();
610 + $courseObjPrepare->course_data->expiration_time = $expirationTime ? $expirationTime->format( LP_Datetime::I18N_FORMAT ) : __( 'Lifetime', 'learnpress' );
611 + $courseObjPrepare->course_data->result = $userCourse->calculate_course_results();
612 + }
613 +
614 + $courseObjPrepare = apply_filters( 'learnPress/prepare_struct_courses_response/courseObjPrepare', $courseObjPrepare, $course );
615 + $data[] = $courseObjPrepare;
616 + }
617 +
618 + return $data;
619 + }
620 +
621 + public function enroll_course( $request ) {
622 + if ( ! class_exists( 'LP_REST_Courses_Controller' ) ) {
623 + include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-courses-controller.php';
624 + }
625 +
626 + $course_controller = new LP_REST_Courses_Controller();
627 +
628 + return $course_controller->enroll_courses( $request );
629 + }
630 +
631 + public function finish_course( $request ) {
632 + $response = new LP_REST_Response();
633 +
634 + try {
635 + $user = learn_press_get_current_user();
636 + $course_id = isset( $request['id'] ) ? wp_unslash( $request['id'] ) : false;
637 +
638 + if ( empty( $course_id ) ) {
639 + throw new Exception( esc_html__( 'Error: No Course ID available.', 'learnpress' ) );
640 + }
641 +
642 + $course = learn_press_get_course( $course_id );
643 + $check = $user->can_show_finish_course_btn( $course );
644 +
645 + if ( $check['status'] !== 'success' ) {
646 + throw new Exception( $check['message'] ?? esc_html__( 'Cannot finish this course.', 'learnpress' ) );
647 + }
648 +
649 + $finished = $user->finish_course( $course_id );
650 +
651 + if ( empty( $finished ) ) {
652 + throw new Exception( esc_html__( 'Error: Cannot finish this course.', 'learnpress' ) );
653 + }
654 +
655 + $response->status = 'success';
656 + $response->message = esc_html__( 'Congrats! You have completed the Course.', 'learnpress' );
657 + } catch ( \Throwable $th ) {
658 + $response->status = 'error';
659 + $response->message = $th->getMessage();
660 + }
661 +
662 + return rest_ensure_response( $response );
663 + }
664 +
665 + public function retake_course( $request ) {
666 + $response = new LP_REST_Response();
667 +
668 + try {
669 + if ( ! class_exists( 'LP_REST_Courses_Controller' ) ) {
670 + include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-courses-controller.php';
671 + }
672 +
673 + $course_controller = new LP_REST_Courses_Controller();
674 + $response = $course_controller->retake_course( $request );
675 + } catch ( Throwable $e ) {
676 + $response->message = $e->getMessage();
677 + }
678 +
679 + return $response;
680 + }
681 +
682 + /**
683 + * Create a single product.
684 + *
685 + * @param WP_REST_Request $request Full details about the request.
686 + *
687 + * @return WP_Error|WP_REST_Response
688 + */
689 + public function create_item( $request ) {
690 + if ( ! empty( $request['id'] ) ) {
691 + return new WP_Error( "lp_rest_{$this->post_type}_exists", sprintf( __( 'Cannot create existing %s.', 'learnpress' ), $this->post_type ), array( 'status' => 400 ) );
692 + }
693 +
694 + $prepared_post = $this->prepare_item_for_database( $request );
695 +
696 + if ( is_wp_error( $prepared_post ) ) {
697 + return $prepared_post;
698 + }
699 +
700 + $prepared_post->post_type = $this->post_type;
701 +
702 + $post_id = wp_insert_post( wp_slash( (array) $prepared_post ), true, false );
703 +
704 + if ( is_wp_error( $post_id ) ) {
705 + if ( 'db_insert_error' === $post_id->get_error_code() ) {
706 + $post_id->add_data( array( 'status' => 500 ) );
707 + } else {
708 + $post_id->add_data( array( 'status' => 400 ) );
709 + }
710 +
711 + return $post_id;
712 + }
713 +
714 + $post = get_post( $post_id );
715 +
716 + do_action( "lp_rest_insert_{$this->post_type}", $post, $request, true );
717 +
718 + $fields_update = $this->update_additional_fields_for_object( $post, $request );
719 +
720 + if ( is_wp_error( $fields_update ) ) {
721 + return $fields_update;
722 + }
723 +
724 + $request->set_param( 'context', 'edit' );
725 +
726 + do_action( "lp_rest_after_insert_{$this->post_type}", $post, $request, true );
727 +
728 + wp_after_insert_post( $post, false, null );
729 +
730 + $object = $this->get_object( $post_id );
731 +
732 + $response = $this->prepare_object_for_response( $object, $request );
733 + $response = rest_ensure_response( $response );
734 +
735 + $response->set_status( 201 );
736 + $response->header( 'Location', rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $object->get_id() ) ) );
737 +
738 + return $response;
739 + }
740 +
741 + public function prepare_object_for_response( $object, $request ) {
742 + $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
743 + $data = $this->get_course_data( $object, $context, $request );
744 +
745 + // Ensure sections are always included for single-item response, regardless of schema/context filtering.
746 + if ( empty( $request['optimize'] ) && ! isset( $data['sections'] ) ) {
747 + $data['sections'] = $this->get_all_items( $object );
748 + }
749 +
750 + $response = rest_ensure_response( $data );
751 +
752 + return apply_filters( "lp_jwt_rest_prepare_{$this->post_type}_object", $response, $object, $request );
753 + }
754 +
755 + /**
756 + * @param LP_Course $course
757 + *
758 + * @throws Exception
759 + */
760 + protected function get_course_data( $course, $context = 'view' ) {
761 + $request = func_num_args() >= 2 ? func_get_arg( 2 ) : new WP_REST_Request( '', '', array( 'context' => $context ) );
762 + $fields = $this->get_fields_for_response( $request );
763 +
764 + $id = $course->get_id();
765 + $post = get_post( $course->get_id() );
766 +
767 + $data = array();
768 +
769 + foreach ( $fields as $field ) {
770 + if ( ! empty( $request['optimize'] ) ) {
771 + $disables = is_bool( $request['optimize'] ) ? 'sections,course_data,instructor,meta_data,tags,can_finish,can_retake,count_students,rataken,ratake_count' : $request['optimize'];
772 + $disable = explode( ',', $disables );
773 +
774 + if ( ! empty( $disable ) && in_array( $field, $disable ) ) {
775 + continue;
776 + }
777 + }
778 +
779 + switch ( $field ) {
780 + case 'id':
781 + $data['id'] = $course->get_id();
782 + break;
783 + case 'name':
784 + $data['name'] = $post->post_title;
785 + break;
786 + case 'slug':
787 + $data['slug'] = $post->post_name;
788 + break;
789 + case 'permalink':
790 + $data['permalink'] = $course->get_permalink();
791 + break;
792 + case 'image':
793 + $data['image'] = $course->get_image_url( 'full' );
794 + break;
795 + case 'date_created':
796 + $data['date_created'] = lp_jwt_prepare_date_response( $post->post_date_gmt, $post->post_date );
797 + break;
798 + case 'date_created_gmt':
799 + $data['date_created_gmt'] = lp_jwt_prepare_date_response( $post->post_date_gmt );
800 + break;
801 + case 'date_modified':
802 + $data['date_modified'] = lp_jwt_prepare_date_response( $post->post_modified_gmt, $post->post_modified );
803 + break;
804 + case 'date_modified_gmt':
805 + $data['date_modified_gmt'] = lp_jwt_prepare_date_response( $post->post_modified_gmt );
806 + break;
807 + case 'on_sale':
808 + $data['on_sale'] = $course->has_sale_price();
809 + break;
810 + case 'status':
811 + $data['status'] = $post->post_status;
812 + break;
813 + case 'content':
814 + $data['content'] = 'view' === $context ? apply_filters( 'the_content', $post->post_content ) : $post->post_content;
815 + break;
816 + case 'excerpt':
817 + $data['excerpt'] = $post->post_excerpt;
818 + break;
819 + case 'count_students':
820 + $data['count_students'] = $course->count_students();
821 + break;
822 + case 'can_finish':
823 + $data['can_finish'] = $this->check_can_finish( $course );
824 + break;
825 + case 'can_retake':
826 + $data['can_retake'] = $this->check_can_retake( $id );
827 + break;
828 + case 'ratake_count':
829 + $data['ratake_count'] = (int) $course->get_data( 'retake_count' );
830 + break;
831 + case 'rataken':
832 + $data['rataken'] = $this->get_retaken_count( $id );
833 + break;
834 + case 'duration':
835 + $data['duration'] = learn_press_get_post_translated_duration( $id, esc_html__( 'Lifetime', 'learnpress' ) );
836 + break;
837 + case 'categories':
838 + $data['categories'] = $this->get_course_taxonomy( $id, 'course_category' );
839 + break;
840 + case 'tags':
841 + $data['tags'] = $this->get_course_taxonomy( $id, 'course_tag' );
842 + break;
843 + case 'instructor':
844 + $data['instructor'] = $this->get_instructor_info( $course );
845 + break;
846 + case 'sections':
847 + $data['sections'] = $this->get_all_items( $course );
848 + break;
849 + case 'course_data':
850 + $data['course_data'] = $this->get_course_data_for_current_user( $id, $request );
851 + break;
852 + case 'rating':
853 + $data['rating'] = $this->get_course_rating( $id );
854 + break;
855 + case 'price':
856 + $data['price'] = floatval( $course->get_price() );
857 + break;
858 + case 'price_rendered':
859 + $data['price_rendered'] = html_entity_decode( $course->get_price_html() );
860 + break;
861 + case 'origin_price':
862 + $data['origin_price'] = floatval( $course->get_origin_price() );
863 + break;
864 + case 'origin_price_rendered':
865 + $data['origin_price_rendered'] = html_entity_decode( $course->get_origin_price_html() );
866 + break;
867 + case 'sale_price':
868 + $data['sale_price'] = floatval( $course->get_sale_price() );
869 + break;
870 + case 'sale_price_rendered':
871 + $data['sale_price_rendered'] = html_entity_decode( learn_press_format_price( $course->get_sale_price(), true ) );
872 + break;
873 + }
874 + }
875 +
876 + $data['meta_data'] = $this->get_course_meta( $id );
877 +
878 + return $data;
879 + }
880 +
881 + /**
882 + * Handle price course return to App
883 + *
884 + * @param CourseModel $course
885 + *
886 + * @return string
887 + * @throws Exception
888 + * @version 1.0.0
889 + * @since 4.2.6.9
890 + */
891 + public function render_course_price( CourseModel $course ): string {
892 + $price_string = '';
893 +
894 + if ( $course->is_free() ) {
895 + $price_string = apply_filters(
896 + 'learn_press_course_price_html_free',
897 + esc_html__( 'Free', 'learnpress' ),
898 + $this
899 + );
900 + } else {
901 + $price_string .= learn_press_format_price( $course->get_price() );
902 + }
903 +
904 + return html_entity_decode( $price_string );
905 + }
906 +
907 + public function get_retaken_count( $id ) {
908 + $user_id = get_current_user_id();
909 +
910 + if ( ! $user_id ) {
911 + return 0;
912 + }
913 +
914 + $user = learn_press_get_user( $user_id );
915 +
916 + if ( ! $user ) {
917 + return 0;
918 + }
919 +
920 + $user_course_data = $user->get_course_data( $id );
921 + if ( ! $user_course_data ) {
922 + return 0;
923 + }
924 +
925 + return absint( $user_course_data->get_retaken_count() );
926 + }
927 +
928 + public function check_can_retake( $id ) {
929 + $user_id = get_current_user_id();
930 +
931 + if ( ! $user_id ) {
932 + return 0;
933 + }
934 +
935 + $user = learn_press_get_user( $user_id );
936 +
937 + if ( $user ) {
938 + $can_retake_times = $user->can_retry_course( $id );
939 +
940 + if ( $can_retake_times ) {
941 + return true;
942 + }
943 +
944 + return false;
945 + }
946 +
947 + return false;
948 + }
949 +
950 + public function get_course_rating( $id ) {
951 + if ( ! function_exists( 'learn_press_get_course_rate' ) ) {
952 + return false;
953 + }
954 +
955 + $course_rate = learn_press_get_course_rate( $id );
956 +
957 + return ! empty( $course_rate ) ? floatval( number_format( $course_rate, 1 ) ) : 0;
958 + }
959 +
960 + public function check_can_finish( $course ) {
961 + $user_id = get_current_user_id();
962 +
963 + if ( ! $user_id ) {
964 + return false;
965 + }
966 +
967 + $user = learn_press_get_user( $user_id );
968 +
969 + if ( $user && $course ) {
970 + $check = $user->can_show_finish_course_btn( $course );
971 +
972 + if ( $check['status'] === 'success' ) {
973 + return true;
974 + }
975 +
976 + return false;
977 + }
978 +
979 + return false;
980 + }
981 +
982 + /**
983 + * Get instructor info
984 + *
985 + * @param LP_Course $course
986 + *
987 + * @return array
988 + */
989 + public function get_instructor_info( $course ) {
990 + if ( ! $course ) {
991 + return [];
992 + }
993 +
994 + if ( $course instanceof LP_Course ) {
995 + $courseModel = CourseModel::find( $course->get_id(), true );
996 + } elseif ( $course instanceof CourseModel ) {
997 + $courseModel = $course;
998 + } else {
999 + return [];
1000 + }
1001 +
1002 + $output = array();
1003 + $extra_info = learn_press_get_user_extra_profile_info( $courseModel->get_id() );
1004 + $author = $courseModel->get_author_model();
1005 + if ( ! $author ) {
1006 + return [];
1007 + }
1008 +
1009 + $output['avatar'] = $author->get_avatar_url();
1010 + $output['id'] = $author->get_id();
1011 + $output['name'] = $author->get_display_name();
1012 + $output['description'] = $author->get_description();
1013 + $output['social'] = $extra_info;
1014 + $output['statistic'] = $this->get_instructor_statistic( $author );
1015 +
1016 + return $output;
1017 + }
1018 +
1019 + /**
1020 + * Count the courses and the students of an instructor.
1021 + *
1022 + * Only used for the single course response, it costs several queries and
1023 + * "LP_Cache::cache_load_first" only keeps the result within the current request.
1024 + *
1025 + * @param UserModel $author
1026 + *
1027 + * @return array
1028 + */
1029 + protected function get_instructor_statistic( UserModel $author ): array {
1030 + $statistic = $author->get_instructor_statistic();
1031 +
1032 + // Keep the extra keys added by add-ons, only cast the ones of the core.
1033 + $numeric_keys = array(
1034 + 'total_course',
1035 + 'published_course',
1036 + 'pending_course',
1037 + 'total_student',
1038 + 'student_completed',
1039 + 'student_in_progress',
1040 + );
1041 +
1042 + foreach ( $numeric_keys as $numeric_key ) {
1043 + $statistic[ $numeric_key ] = absint( $statistic[ $numeric_key ] ?? 0 );
1044 + }
1045 +
1046 + return $statistic;
1047 + }
1048 +
1049 + /**
1050 + * Get instructor info
1051 + *
1052 + * @param UserModel $author
1053 + *
1054 + * @return array
1055 + */
1056 + public function get_author_info( $author ): array {
1057 + $output = [];
1058 + $output['avatar'] = $author->get_avatar_url();
1059 + $output['id'] = $author->get_id();
1060 + $output['name'] = $author->display_name;
1061 + $output['description'] = $author->get_description();
1062 +
1063 + return $output;
1064 + }
1065 +
1066 + public function get_item_learned_ids( $request ) {
1067 + global $wpdb;
1068 +
1069 + $user_id = get_current_user_id();
1070 +
1071 + if ( ! $user_id ) {
1072 + return false;
1073 + }
1074 +
1075 + $filter = ! empty( $request['course_filter'] ) ? $request['course_filter'] : false;
1076 + $where = $wpdb->prepare( 'user_id=%d AND item_type=%s', $user_id, $this->post_type ); // phpcs:ignore
1077 +
1078 + if ( $filter ) {
1079 + if ( $filter === 'in-progress' ) {
1080 + $where .= $wpdb->prepare( ' AND status=%s AND graduation=%s', 'enrolled', 'in-progress' );
1081 + } elseif ( in_array( $filter, array( 'passed', 'failed' ) ) ) { // is "passed" or "failed"
1082 + $where .= $wpdb->prepare( ' AND status=%s AND graduation=%s', 'finished', $filter );
1083 + }
1084 + }
1085 +
1086 + $query = "SELECT item_id FROM {$wpdb->prefix}learnpress_user_items WHERE {$where}";
1087 +
1088 + $item_ids = $wpdb->get_col( $query );
1089 +
1090 + return $item_ids;
1091 + }
1092 +
1093 + public function get_course_data_for_current_user( $id, $request ) {
1094 + $user = learn_press_get_user( get_current_user_id() );
1095 +
1096 + if ( empty( $user ) || empty( $id ) ) {
1097 + return array();
1098 + }
1099 +
1100 + $course_data = $user->get_course_data( $id );
1101 +
1102 + if ( ! $course_data ) {
1103 + return;
1104 + }
1105 +
1106 + return array(
1107 + 'graduation' => $course_data->get_graduation() ?? '',
1108 + 'status' => $course_data->get_status() ?? '',
1109 + 'start_time' => $course_data->get_start_time() ? lp_jwt_prepare_date_response( $course_data->get_start_time()->toSql() ) : null,
1110 + 'end_time' => $course_data->get_end_time() ? lp_jwt_prepare_date_response( $course_data->get_end_time()->toSql() ) : null,
1111 + 'expiration_time' => $course_data->get_expiration_time() ? lp_jwt_prepare_date_response( $course_data->get_expiration_time()->toSql() ) : '',
1112 + 'result' => $course_data->calculate_course_results(),
1113 + );
1114 + }
1115 +
1116 + public function get_course_taxonomy( $id, $taxonomy ) {
1117 + $terms = get_the_terms( $id, $taxonomy );
1118 + $output = array();
1119 +
1120 + if ( $terms ) {
1121 + foreach ( $terms as $term ) {
1122 + $output[] = array(
1123 + 'id' => $term->term_id,
1124 + 'name' => $term->name,
1125 + 'slug' => $term->slug,
1126 + );
1127 + }
1128 + }
1129 +
1130 + return $output;
1131 + }
1132 +
1133 + /**
1134 + * Get Items of sections
1135 + *
1136 + * @param LP_Course $course
1137 + *
1138 + * @throws Exception
1139 + * @editor tungnx
1140 + * @modify 4.1.3
1141 + * @version 4.0.1
1142 + */
1143 + public function get_all_items( $course ): array {
1144 + $curriculum = $course->get_curriculum();
1145 + $user = learn_press_get_current_user();
1146 + $user_course = $user ? $user->get_course_data( $course->get_id() ) : false;
1147 + $output = array();
1148 +
1149 + if ( ! empty( $curriculum ) ) {
1150 + foreach ( $curriculum as $section ) {
1151 + if ( $section ) {
1152 + $data = array(
1153 + 'id' => $section->get_id(),
1154 + 'title' => $section->get_title(),
1155 + 'course_id' => $section->get_course_id(),
1156 + 'description' => $section->get_description(),
1157 + 'order' => $section->get_order(),
1158 + );
1159 +
1160 + if ( $user_course && $user->has_enrolled_or_finished( $section->get_course_id() ) ) {
1161 + $data['percent'] = $user_course->get_percent_completed_items( '', $section->get_id() );
1162 + }
1163 +
1164 + $data_item = array();
1165 +
1166 + $items = $section->get_items();
1167 +
1168 + if ( ! empty( $items ) ) {
1169 + foreach ( $items as $item ) {
1170 + $post = get_post( $item->get_id() );
1171 +
1172 + $format = array(
1173 + 'day' => __( '%s days', 'learnpress' ),
1174 + 'hour' => __( '%s hours', 'learnpress' ),
1175 + 'minute' => __( '%s mins', 'learnpress' ),
1176 + 'second' => __( '%s secs', 'learnpress' ),
1177 + );
1178 +
1179 + $user_item = $user_course ? $user_course->get_item( $item->get_id() ) : false;
1180 +
1181 + if ( $user ) {
1182 + $can_view_content_course = $user->can_view_content_course( absint( $section->get_course_id() ) );
1183 + $can_view_item = $user->can_view_item( $item->get_id(), $can_view_content_course );
1184 + }
1185 +
1186 + $data_item[] = array(
1187 + 'id' => $item->get_id(),
1188 + 'type' => $item->get_item_type(),
1189 + 'slug' => $post->post_name,
1190 + 'title' => $post->post_title,
1191 + 'preview' => $item->is_preview(),
1192 + 'duration' => $item->get_duration()->to_timer( $format, true ),
1193 + 'graduation' => $user_item ? $user_item->get_graduation() : '',
1194 + 'status' => $user_item ? $user_item->get_status() : '',
1195 + 'locked' => ! isset( $can_view_item->flag ) || ! $can_view_item->flag,
1196 + );
1197 + }
1198 + }
1199 +
1200 + $data['items'] = $data_item;
1201 + $output[] = $data;
1202 + }
1203 + }
1204 + }
1205 +
1206 + return $output;
1207 + }
1208 +
1209 + /**
1210 + * Get all settings of the course metabox.
1211 + *
1212 + * @param int $id Course ID.
1213 + *
1214 + * @return array
1215 + */
1216 + public function get_course_meta( $id ) {
1217 + if ( empty( absint( $id ) ) ) {
1218 + return array();
1219 + }
1220 +
1221 + if ( ! class_exists( 'LP_Meta_Box' ) ) {
1222 + include_once LP_PLUGIN_PATH . 'inc/admin/views/meta-boxes/class-lp-meta-box.php';
1223 + }
1224 +
1225 + if ( ! class_exists( 'LP_Meta_Box_Course' ) ) {
1226 + include_once LP_PLUGIN_PATH . 'inc/admin/views/meta-boxes/course/settings.php';
1227 + }
1228 +
1229 + $metabox = LP_Meta_Box_Course::instance();
1230 +
1231 + $output = array();
1232 +
1233 + foreach ( $metabox->metabox( $id ) as $key => $tab ) {
1234 + if ( isset( $tab['content'] ) ) {
1235 + foreach ( $tab['content'] as $meta_key => $object ) {
1236 + if ( is_a( $object, 'LP_Meta_Box_Field' ) ) {
1237 + $object->id = $meta_key;
1238 + $output[ $meta_key ] = $object->meta_value( $id );
1239 + }
1240 + }
1241 + }
1242 + }
1243 +
1244 + return $output;
1245 + }
1246 +
1247 + protected function prepare_objects_query( $request ) {
1248 + $args = parent::prepare_objects_query( $request );
1249 +
1250 + $taxonomies = array(
1251 + 'course_category' => 'category',
1252 + 'course_tag' => 'tag',
1253 + );
1254 +
1255 + foreach ( $taxonomies as $taxonomy => $key ) {
1256 + if ( ! empty( $request[ $key ] ) ) {
1257 + $tax_query[] = array(
1258 + 'taxonomy' => $taxonomy,
1259 + 'field' => 'term_id',
1260 + 'terms' => $request[ $key ],
1261 + );
1262 + }
1263 + }
1264 +
1265 + if ( ! empty( $tax_query ) ) {
1266 + $args['tax_query'] = $tax_query;
1267 + }
1268 +
1269 + $orderby = $request->get_param( 'orderby' );
1270 + $order = $request->get_param( 'order' );
1271 +
1272 + $orderby = strtolower( is_array( $orderby ) ? (string) current( $orderby ) : (string) $orderby );
1273 + $order = strtoupper( is_array( $order ) ? (string) current( $order ) : (string) $order );
1274 +
1275 + switch ( $orderby ) {
1276 + case 'id':
1277 + $args['orderby'] = 'ID';
1278 + break;
1279 + case 'menu_order':
1280 + $args['orderby'] = 'menu_order title';
1281 + break;
1282 + case 'title':
1283 + $args['orderby'] = 'post_title';
1284 + $args['order'] = ( 'DESC' === $order ) ? 'DESC' : 'ASC';
1285 + break;
1286 + case 'relevance':
1287 + $args['orderby'] = 'relevance';
1288 + $args['order'] = 'DESC';
1289 + break;
1290 + case 'rand':
1291 + $args['orderby'] = 'rand'; // @codingStandardsIgnoreLine
1292 + break;
1293 + case 'date':
1294 + $args['orderby'] = 'date ID';
1295 + $args['order'] = ( 'ASC' === $order ) ? 'ASC' : 'DESC';
1296 + break;
1297 + case 'price':
1298 + $args['orderby'] = 'meta_value_num';
1299 + $args['meta_key'] = '_lp_price';
1300 + break;
1301 + }
1302 +
1303 + if ( is_bool( $request['on_sale'] ) ) {
1304 + $on_sale_key = $request['on_sale'] ? 'post__in' : 'post__not_in';
1305 +
1306 + $filter = new LP_Course_Filter();
1307 + $filter->only_fields = array( 'ID' );
1308 +
1309 + $filter = LP_Course_DB::getInstance()->get_courses_sort_by_sale( $filter );
1310 + $on_sale_ids = LP_Course_DB::getInstance()->get_courses( $filter );
1311 + $on_sale_ids = LP_Database::get_values_by_key( $on_sale_ids );
1312 + $on_sale_ids = empty( $on_sale_ids ) ? array( 0 ) : $on_sale_ids;
1313 +
1314 + $args[ $on_sale_key ] += $on_sale_ids;
1315 + } elseif ( is_bool( $request['popular'] ) ) {
1316 + $on_popular_key = $request['popular'] ? 'post__in' : 'post__not_in';
1317 +
1318 + $filter = new LP_Course_Filter();
1319 + $filter->only_fields = array( 'ID' );
1320 + $filter->limit = $request['per_page'] ?? 10;
1321 + $filter->page = $request['page'] ?? 1;
1322 +
1323 + $filter = LP_Course_DB::getInstance()->get_courses_order_by_popular( $filter );
1324 + $on_popular_ids = LP_Course_DB::getInstance()->get_courses( $filter );
1325 + $on_popular_ids = LP_Database::get_values_by_key( $on_popular_ids );
1326 +
1327 + $on_popular_ids = empty( $on_popular_ids ) ? array( 0 ) : $on_popular_ids;
1328 +
1329 + $args[ $on_popular_key ] += $on_popular_ids;
1330 +
1331 + $args['orderby'] = 'post__in';
1332 + }
1333 +
1334 + return $args;
1335 + }
1336 +
1337 + public function get_item_schema() {
1338 + $schema = array(
1339 + '$schema' => 'http://json-schema.org/draft-04/schema#',
1340 + 'title' => $this->post_type,
1341 + 'type' => 'object',
1342 + 'properties' => array(
1343 + 'id' => array(
1344 + 'description' => __( 'A unique identifier for the resource.', 'learnpress' ),
1345 + 'type' => 'integer',
1346 + 'context' => array( 'view', 'edit' ),
1347 + 'readonly' => true,
1348 + ),
1349 + 'name' => array(
1350 + 'description' => __( 'Course name.', 'learnpress' ),
1351 + 'type' => 'string',
1352 + 'context' => array( 'view', 'edit' ),
1353 + ),
1354 + 'slug' => array(
1355 + 'description' => __( 'Course slug.', 'learnpress' ),
1356 + 'type' => 'string',
1357 + 'context' => array( 'view', 'edit' ),
1358 + ),
1359 + 'permalink' => array(
1360 + 'description' => __( 'Course URL.', 'learnpress' ),
1361 + 'type' => 'string',
1362 + 'format' => 'uri',
1363 + 'context' => array( 'view', 'edit' ),
1364 + 'readonly' => true,
1365 + ),
1366 + 'image' => array(
1367 + 'description' => __( 'Course Image URL.', 'learnpress' ),
1368 + 'type' => 'string',
1369 + 'format' => 'uri',
1370 + 'context' => array( 'view', 'edit' ),
1371 + ),
1372 + 'date_created' => array(
1373 + 'description' => __( "The date the Course was created, in the site's timezone.", 'learnpress' ),
1374 + 'type' => array( 'string', 'null' ),
1375 + 'format' => 'date-time',
1376 + 'context' => array( 'view', 'edit' ),
1377 + 'readonly' => true,
1378 + ),
1379 + 'date_created_gmt' => array(
1380 + 'description' => __( 'The date the Course was created, as GMT.', 'learnpress' ),
1381 + 'type' => array( 'string', 'null' ),
1382 + 'format' => 'date-time',
1383 + 'context' => array( 'view', 'edit' ),
1384 + 'readonly' => true,
1385 + ),
1386 + 'date_modified' => array(
1387 + 'description' => __( "The date the Course was last modified, in the site's timezone.", 'learnpress' ),
1388 + 'type' => array( 'string', 'null' ),
1389 + 'format' => 'date-time',
1390 + 'context' => array( 'view', 'edit' ),
1391 + 'readonly' => true,
1392 + ),
1393 + 'date_modified_gmt' => array(
1394 + 'description' => __( 'The date the Course was last modified, as GMT.', 'learnpress' ),
1395 + 'type' => array( 'string', 'null' ),
1396 + 'format' => 'date-time',
1397 + 'context' => array( 'view', 'edit' ),
1398 + 'readonly' => true,
1399 + ),
1400 + 'on_sale' => array(
1401 + 'description' => __( 'Display courses if they are on sale.', 'learnpress' ),
1402 + 'type' => 'boolean',
1403 + 'context' => array( 'view', 'edit' ),
1404 + 'readonly' => true,
1405 + ),
1406 + 'status' => array(
1407 + 'description' => __( 'Course status (post status).', 'learnpress' ),
1408 + 'type' => 'string',
1409 + 'default' => 'publish',
1410 + 'enum' => array_merge( array_keys( get_post_statuses() ), array( 'future' ) ),
1411 + 'context' => array( 'view', 'edit' ),
1412 + ),
1413 + 'content' => array(
1414 + 'description' => __( 'Course content.', 'learnpress' ),
1415 + 'type' => 'string',
1416 + 'context' => array( 'view', 'edit' ),
1417 + ),
1418 + 'excerpt' => array(
1419 + 'description' => __( 'Retrieves the course excerpt..', 'learnpress' ),
1420 + 'type' => 'string',
1421 + 'context' => array( 'view', 'edit' ),
1422 + ),
1423 + 'duration' => array(
1424 + 'description' => __( 'Duration', 'learnpress' ),
1425 + 'type' => 'string',
1426 + 'context' => array( 'view' ),
1427 + ),
1428 + 'count_students' => array(
1429 + 'description' => __( 'Count the number of enrolled students.', 'learnpress' ),
1430 + 'type' => 'integer',
1431 + 'context' => array( 'view' ),
1432 + 'readonly' => true,
1433 + ),
1434 + 'can_finish' => array(
1435 + 'description' => __( 'Can finish the course', 'learnpress' ),
1436 + 'type' => 'boolean',
1437 + 'context' => array( 'view' ),
1438 + 'readonly' => true,
1439 + ),
1440 + 'can_retake' => array(
1441 + 'description' => __( 'Can retake the course', 'learnpress' ),
1442 + 'type' => 'boolean',
1443 + 'context' => array( 'view' ),
1444 + 'readonly' => true,
1445 + ),
1446 + 'ratake_count' => array(
1447 + 'description' => __( 'Total retakes', 'learnpress' ),
1448 + 'type' => 'integer',
1449 + 'context' => array( 'view' ),
1450 + 'readonly' => true,
1451 + ),
1452 + 'rataken' => array(
1453 + 'description' => __( 'Retaken', 'learnpress' ),
1454 + 'type' => 'integer',
1455 + 'context' => array( 'view' ),
1456 + 'readonly' => true,
1457 + ),
1458 + 'rating' => array(
1459 + 'description' => __( 'Course Review add-on', 'learnpress' ),
1460 + 'type' => array( 'boolean', 'integer' ),
1461 + 'context' => array( 'view' ),
1462 + 'readonly' => true,
1463 + ),
1464 + 'price' => array(
1465 + 'description' => __( 'Course Price', 'learnpress' ),
1466 + 'type' => 'integer',
1467 + 'context' => array( 'view' ),
1468 + 'readonly' => true,
1469 + ),
1470 + 'price_rendered' => array(
1471 + 'description' => __( 'Course Price Rendered', 'learnpress' ),
1472 + 'type' => 'string',
1473 + 'context' => array( 'view' ),
1474 + 'readonly' => true,
1475 + ),
1476 + 'origin_price' => array(
1477 + 'description' => __( 'Course Origin Price', 'learnpress' ),
1478 + 'type' => 'integer',
1479 + 'context' => array( 'view' ),
1480 + 'readonly' => true,
1481 + ),
1482 + 'origin_price_rendered' => array(
1483 + 'description' => __( 'Course Origin Price Rendered', 'learnpress' ),
1484 + 'type' => 'string',
1485 + 'context' => array( 'view' ),
1486 + 'readonly' => true,
1487 + ),
1488 + 'sale_price' => array(
1489 + 'description' => __( 'Course Sale Price', 'learnpress' ),
1490 + 'type' => 'integer',
1491 + 'context' => array( 'view' ),
1492 + 'readonly' => true,
1493 + ),
1494 + 'sale_price_rendered' => array(
1495 + 'description' => __( 'Course Sale Price Rendered', 'learnpress' ),
1496 + 'type' => 'string',
1497 + 'context' => array( 'view' ),
1498 + 'readonly' => true,
1499 + ),
1500 + 'categories' => array(
1501 + 'description' => __( 'List of categories.', 'learnpress' ),
1502 + 'type' => 'array',
1503 + 'context' => array( 'view', 'edit' ),
1504 + 'items' => array(
1505 + 'type' => 'object',
1506 + 'properties' => array(
1507 + 'id' => array(
1508 + 'description' => __( 'Category ID.', 'learnpress' ),
1509 + 'type' => 'integer',
1510 + 'context' => array( 'view', 'edit' ),
1511 + ),
1512 + 'name' => array(
1513 + 'description' => __( 'Category name.', 'learnpress' ),
1514 + 'type' => 'string',
1515 + 'context' => array( 'view', 'edit' ),
1516 + 'readonly' => true,
1517 + ),
1518 + 'slug' => array(
1519 + 'description' => __( 'Category slug.', 'learnpress' ),
1520 + 'type' => 'string',
1521 + 'context' => array( 'view', 'edit' ),
1522 + 'readonly' => true,
1523 + ),
1524 + ),
1525 + ),
1526 + ),
1527 + 'tags' => array(
1528 + 'description' => __( 'List of tags.', 'learnpress' ),
1529 + 'type' => 'array',
1530 + 'context' => array( 'view', 'edit' ),
1531 + 'items' => array(
1532 + 'type' => 'object',
1533 + 'properties' => array(
1534 + 'id' => array(
1535 + 'description' => __( 'Tag ID.', 'learnpress' ),
1536 + 'type' => 'integer',
1537 + 'context' => array( 'view', 'edit' ),
1538 + ),
1539 + 'name' => array(
1540 + 'description' => __( 'Tag name.', 'learnpress' ),
1541 + 'type' => 'string',
1542 + 'context' => array( 'view', 'edit' ),
1543 + 'readonly' => true,
1544 + ),
1545 + 'slug' => array(
1546 + 'description' => __( 'Tag slug.', 'learnpress' ),
1547 + 'type' => 'string',
1548 + 'context' => array( 'view', 'edit' ),
1549 + 'readonly' => true,
1550 + ),
1551 + ),
1552 + ),
1553 + ),
1554 + 'instructor' => array(
1555 + 'description' => __( 'Retrieves the course sections and items..', 'learnpress' ),
1556 + 'type' => 'array',
1557 + 'context' => array( 'view', 'edit' ),
1558 + 'items' => array(
1559 + 'type' => 'object',
1560 + 'properties' => array(
1561 + 'id' => array(
1562 + 'description' => __( 'User ID.', 'learnpress' ),
1563 + 'type' => 'integer',
1564 + 'context' => array( 'view' ),
1565 + ),
1566 + 'name' => array(
1567 + 'description' => __( 'Display name.', 'learnpress' ),
1568 + 'type' => 'string',
1569 + 'context' => array( 'view' ),
1570 + 'readonly' => true,
1571 + ),
1572 + 'description' => array(
1573 + 'description' => __( 'Tag slug.', 'learnpress' ),
1574 + 'type' => 'string',
1575 + 'context' => array( 'view' ),
1576 + 'readonly' => true,
1577 + ),
1578 + 'social' => array(
1579 + 'description' => __( 'Social Infor.', 'learnpress' ),
1580 + 'type' => 'array',
1581 + 'context' => array( 'view' ),
1582 + 'readonly' => true,
1583 + ),
1584 + 'statistic' => array(
1585 + 'description' => __( 'Total courses and students of the instructor.', 'learnpress' ),
1586 + 'type' => 'object',
1587 + 'context' => array( 'view' ),
1588 + 'readonly' => true,
1589 + ),
1590 + ),
1591 + ),
1592 + ),
1593 + 'sections' => array(
1594 + 'description' => __( 'Retrieves the course sections and items..', 'learnpress' ),
1595 + 'type' => 'array',
1596 + 'context' => array( 'view', 'edit' ),
1597 + 'items' => array(
1598 + 'type' => 'object',
1599 + 'properties' => array(
1600 + 'id' => array(
1601 + 'description' => __( 'Section ID.', 'learnpress' ),
1602 + 'type' => 'integer',
1603 + 'context' => array( 'view', 'edit' ),
1604 + ),
1605 + 'title' => array(
1606 + 'description' => __( 'Section name.', 'learnpress' ),
1607 + 'type' => 'string',
1608 + 'context' => array( 'view', 'edit' ),
1609 + ),
1610 + 'course_id' => array(
1611 + 'description' => __( 'Course ID.', 'learnpress' ),
1612 + 'type' => 'integer',
1613 + 'context' => array( 'view', 'edit' ),
1614 + ),
1615 + 'description' => array(
1616 + 'description' => __( 'Section description.', 'learnpress' ),
1617 + 'type' => 'string',
1618 + 'context' => array( 'view', 'edit' ),
1619 + ),
1620 + 'items' => array(
1621 + 'description' => __( 'Section items.', 'learnpress' ),
1622 + 'type' => 'array',
1623 + 'context' => array( 'view', 'edit' ),
1624 + 'items' => array(
1625 + 'id' => array(
1626 + 'description' => __( 'Item ID.', 'learnpress' ),
1627 + 'type' => 'integer',
1628 + 'context' => array( 'view', 'edit' ),
1629 + ),
1630 + 'type' => array(
1631 + 'description' => __( 'Item Type.', 'learnpress' ),
1632 + 'type' => 'string',
1633 + 'context' => array( 'view', 'edit' ),
1634 + ),
1635 + 'slug' => array(
1636 + 'description' => __( 'Item slug.', 'learnpress' ),
1637 + 'type' => 'string',
1638 + 'context' => array( 'view', 'edit' ),
1639 + ),
1640 + 'title' => array(
1641 + 'description' => __( 'Item title.', 'learnpress' ),
1642 + 'type' => 'string',
1643 + 'context' => array( 'view', 'edit' ),
1644 + ),
1645 + 'preview' => array(
1646 + 'description' => __( 'Item ID.', 'learnpress' ),
1647 + 'type' => 'boolean',
1648 + 'context' => array( 'view', 'edit' ),
1649 + ),
1650 + 'percent' => array(
1651 + 'description' => __( 'Percent.', 'learnpress' ),
1652 + 'type' => 'integer',
1653 + 'context' => array( 'view', 'edit' ),
1654 + ),
1655 + 'duration' => array(
1656 + 'description' => __( 'Duration.', 'learnpress' ),
1657 + 'type' => 'string',
1658 + 'context' => array( 'view', 'edit' ),
1659 + ),
1660 + 'graduation' => array(
1661 + 'description' => __( 'Graduation.', 'learnpress' ),
1662 + 'type' => 'string',
1663 + 'context' => array( 'view', 'edit' ),
1664 + ),
1665 + 'status' => array(
1666 + 'description' => __( 'Status.', 'learnpress' ),
1667 + 'type' => 'string',
1668 + 'context' => array( 'view', 'edit' ),
1669 + ),
1670 + 'locked' => array(
1671 + 'description' => __( 'Locked.', 'learnpress' ),
1672 + 'type' => 'boolean',
1673 + 'context' => array( 'view', 'edit' ),
1674 + ),
1675 + ),
1676 + ),
1677 + ),
1678 + ),
1679 + ),
1680 + 'course_data' => array(
1681 + 'description' => __( 'List of course user data.', 'learnpress' ),
1682 + 'type' => 'array',
1683 + 'context' => array( 'view' ),
1684 + 'items' => array(
1685 + 'type' => 'object',
1686 + 'properties' => array(
1687 + 'graduation' => array(
1688 + 'description' => __( 'Graduation', 'learnpress' ),
1689 + 'type' => 'string',
1690 + 'context' => array( 'view' ),
1691 + ),
1692 + 'status' => array(
1693 + 'description' => __( 'Status', 'learnpress' ),
1694 + 'type' => 'string',
1695 + 'context' => array( 'view' ),
1696 + 'readonly' => true,
1697 + ),
1698 + 'start_time' => array(
1699 + 'description' => __( 'Start time', 'learnpress' ),
1700 + 'type' => 'string',
1701 + 'context' => array( 'view' ),
1702 + 'readonly' => true,
1703 + ),
1704 + 'end_time' => array(
1705 + 'description' => __( 'End time', 'learnpress' ),
1706 + 'type' => 'string',
1707 + 'context' => array( 'view' ),
1708 + 'readonly' => true,
1709 + ),
1710 + 'expiration_time' => array(
1711 + 'description' => __( 'Expiration time', 'learnpress' ),
1712 + 'type' => 'string',
1713 + 'context' => array( 'view' ),
1714 + 'readonly' => true,
1715 + ),
1716 + ),
1717 + ),
1718 + ),
1719 + ),
1720 + );
1721 +
1722 + return $this->add_additional_fields_schema( $schema );
1723 + }
1724 +
1725 + public function get_collection_params() {
1726 + $params = parent::get_collection_params();
1727 +
1728 + $params['orderby']['enum'] = array_merge( $params['orderby']['enum'], array( 'menu_order', 'price' ) );
1729 +
1730 + $params['category'] = array(
1731 + 'description' => 'Limit the result set to courses assigned to a specific category ID.',
1732 + 'type' => 'string',
1733 + 'sanitize_callback' => 'wp_parse_id_list',
1734 + 'validate_callback' => 'rest_validate_request_arg',
1735 + );
1736 + $params['tag'] = array(
1737 + 'description' => 'Limit the result set to courses assigned to a specific tag ID.',
1738 + 'type' => 'string',
1739 + 'sanitize_callback' => 'wp_parse_id_list',
1740 + 'validate_callback' => 'rest_validate_request_arg',
1741 + );
1742 + $params['course_filter'] = array(
1743 + 'description' => 'Filter by course to in-progress, passed, failed.',
1744 + 'type' => 'string',
1745 + 'sanitize_callback' => 'sanitize_text_field',
1746 + 'validate_callback' => 'rest_validate_request_arg',
1747 + );
1748 +
1749 + $params['on_sale'] = array(
1750 + 'description' => 'Get item learned by user.',
1751 + 'type' => 'boolean',
1752 + 'validate_callback' => 'rest_validate_request_arg',
1753 + );
1754 +
1755 + $params['popular'] = array(
1756 + 'description' => 'Get item popularity.',
1757 + 'type' => 'boolean',
1758 + 'validate_callback' => 'rest_validate_request_arg',
1759 + );
1760 + $params['optimize'] = array(
1761 + 'description' => 'Disable some fields in the schema.',
1762 + 'type' => array( 'boolean', 'string' ),
1763 + 'validate_callback' => 'wp_parse_id_list',
1764 + );
1765 +
1766 + return $params;
1767 + }
1768 +}