PluginProbe
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses / 4.4.10
LearnPress – WordPress LMS Plugin for Create and Sell Online Courses v4.4.10
4.4.10 4.4.9.1 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 All 142 releases
← All changes | inc/jwt/rest-api/version1/class-lp-rest-courses-v1-controller.php +432 -69 4.1.7.2 → 4.4.10 View file →
@@ -1,5 +1,13 @@
1 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 +
2 10 class LP_Jwt_Courses_V1_Controller extends LP_REST_Jwt_Posts_Controller {
3 11 protected $namespace = 'learnpress/v1';
4 12
5 13 protected $rest_base = 'courses';
@@ -14,9 +22,9 @@
14 22 '/' . $this->rest_base,
15 23 array(
16 24 array(
17 25 'methods' => WP_REST_Server::READABLE,
18 - 'callback' => array( $this, 'get_items' ),
26 + 'callback' => array( $this, 'get_courses' ),
19 27 'permission_callback' => array( $this, 'get_items_permissions_check' ),
20 28 'args' => $this->get_collection_params(),
21 29 ),
22 30 array(
@@ -34,9 +42,9 @@
34 42 '/' . $this->rest_base . '/(?P<id>[\d]+)',
35 43 array(
36 44 'args' => array(
37 45 'id' => array(
38 - 'description' => esc_html__( 'Unique identifier for the resource.', 'learnpress' ),
46 + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
39 47 'type' => 'integer',
40 48 ),
41 49 ),
42 50 array(
@@ -60,9 +68,9 @@
60 68 '/' . $this->rest_base . '/enroll',
61 69 array(
62 70 'args' => array(
63 71 'id' => array(
64 - 'description' => esc_html__( 'Unique identifier for the resource.', 'learnpress' ),
72 + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
65 73 'type' => 'integer',
66 74 ),
67 75 ),
68 76 array(
@@ -85,9 +93,9 @@
85 93 '/' . $this->rest_base . '/finish',
86 94 array(
87 95 'args' => array(
88 96 'id' => array(
89 - 'description' => esc_html__( 'Unique identifier for the resource.', 'learnpress' ),
97 + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
90 98 'type' => 'integer',
91 99 ),
92 100 ),
93 101 array(
@@ -110,9 +118,9 @@
110 118 '/' . $this->rest_base . '/retake',
111 119 array(
112 120 'args' => array(
113 121 'id' => array(
114 - 'description' => esc_html__( 'Unique identifier for the resource.', 'learnpress' ),
122 + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
115 123 'type' => 'integer',
116 124 ),
117 125 ),
118 126 array(
@@ -182,12 +190,12 @@
182 190 * Checks if a course can be read.
183 191 *
184 192 * Correctly handles courses with the inherit status.
185 193 *
186 - * @author Nhamdv
194 + * @return bool Whether the post can be read.
195 + * *@author Nhamdv
187 196 *
188 - * @return bool Whether the post can be read.
189 - * */
197 + */
190 198 public function check_read_permission( $post_id ) {
191 199 if ( empty( absint( $post_id ) ) ) {
192 200 return false;
193 201 }
@@ -247,10 +255,13 @@
247 255 public function verify_receipt( $request ) {
248 256 $response = new LP_REST_Response();
249 257 $receipt = ! empty( $request['receipt-data'] ) ? $request['receipt-data'] : '';
250 258 $is_ios = ! empty( $request['is-ios'] ) ? true : false;
251 - $password = LP_Settings::instance()->get( 'in_app_purchase_apple_shared_secret', '' );
252 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 +
253 264 try {
254 265 if ( empty( $receipt ) ) {
255 266 throw new Exception( __( 'Receipt data is empty.', 'learnpress' ) );
256 267 }
@@ -258,9 +269,9 @@
258 269 if ( $is_ios ) {
259 270 $course_id = ! empty( $request['course-id'] ) ? absint( $request['course-id'] ) : 0;
260 271
261 272 if ( empty( $password ) ) {
262 - throw new Exception( __( 'Secret key is empty.', 'learnpress' ) );
273 + throw new Exception( __( 'The secret key is empty.', 'learnpress' ) );
263 274 }
264 275
265 276 $url = LP_Settings::instance()->get( 'in_app_purchase_apple_sandbox' ) === 'yes' ? 'https://sandbox.itunes.apple.com/verifyReceipt' : 'https://buy.itunes.apple.com/verifyReceipt';
266 277
@@ -284,19 +295,19 @@
284 295
285 296 $body = json_decode( wp_remote_retrieve_body( $verify ) );
286 297
287 298 if ( $body->status !== 0 ) {
288 - throw new Exception( __( 'Cannot verify receipt', 'learnpress' ) );
299 + throw new Exception( __( 'Cannot verify the receipt', 'learnpress' ) );
289 300 }
290 301
291 302 $latest_receipt_info = ! empty( $body->latest_receipt_info ) ? $body->latest_receipt_info : array();
292 303
293 304 if ( empty( $latest_receipt_info ) ) {
294 - throw new Exception( __( 'Course id is invalid.', 'learnpress' ) );
305 + throw new Exception( __( 'The course ID is invalid.', 'learnpress' ) );
295 306 }
296 307
297 308 $course_ids = array_map(
298 - function( $receipt_id ) {
309 + function ( $receipt_id ) {
299 310 return absint( $receipt_id->product_id );
300 311 },
301 312 $latest_receipt_info
302 313 );
@@ -301,9 +312,9 @@
301 312 $latest_receipt_info
302 313 );
303 314
304 315 if ( ! in_array( $course_id, $course_ids ) ) {
305 - throw new Exception( __( 'Course id is invalid.', 'learnpress' ) );
316 + throw new Exception( __( 'The course ID is invalid.', 'learnpress' ) );
306 317 }
307 318 } else {
308 319 $receipt = json_decode( $receipt, true );
309 320 $package_name = $receipt['packageName'] ?? '';
@@ -310,14 +321,23 @@
310 321 $course_id = ! empty( $receipt['productId'] ) ? absint( $receipt['productId'] ) : 0;
311 322 $purchase_token = $receipt['purchaseToken'] ?? '';
312 323
313 324 if ( ! function_exists( 'learnpress_in_app_purchase_get_access_token' ) ) {
314 - throw new Exception( __( 'Cannot verify receipt', 'learnpress' ) );
325 + throw new Exception( __( 'Cannot verify the receipt', 'learnpress' ) );
315 326 }
316 327
317 - $access_token = learnpress_in_app_purchase_get_access_token();
328 + $access_token = function_exists( 'learnpress_mobile_iap_settings' ) ? learnpress_in_app_purchase_get_access_token( $platform ) : learnpress_in_app_purchase_get_access_token();
318 329
319 - $verify = wp_remote_get( 'https://androidpublisher.googleapis.com/androidpublisher/v3/applications/' . $package_name . '/purchases/products/' . $course_id . '/tokens/' . $purchase_token . '?access_token=' . $access_token );
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 + );
320 340
321 341 $body = json_decode( wp_remote_retrieve_body( $verify ) );
322 342
323 343 if ( isset( $body->error->message ) ) {
@@ -327,9 +347,9 @@
327 347
328 348 $course = learn_press_get_course( $course_id );
329 349
330 350 if ( ! $course ) {
331 - throw new Exception( __( 'Course is not exists.', 'learnpress' ) );
351 + throw new Exception( __( 'The course ID is invalid.', 'learnpress' ) );
332 352 }
333 353 $user = learn_press_get_current_user();
334 354
335 355 $cart = LearnPress::instance()->cart;
@@ -335,30 +355,23 @@
335 355 $cart = LearnPress::instance()->cart;
336 356 $checkout = LP_Checkout::instance();
337 357
338 358 if ( ! learn_press_enable_cart() ) {
339 - $order_awaiting_payment = LearnPress::instance()->session->order_awaiting_payment;
359 + // $order_awaiting_payment = LearnPress::instance()->session->get( 'order_awaiting_payment' );
340 360 $cart->empty_cart();
341 - LearnPress::instance()->session->order_awaiting_payment = $order_awaiting_payment;
361 + // LearnPress::instance()->session->set( 'order_awaiting_payment', $order_awaiting_payment );
342 362 }
343 363
344 364 $cart_id = $cart->add_to_cart( $course_id, 1, array() );
345 365
346 366 if ( ! $cart_id ) {
347 - throw new Exception( esc_html__( 'Error: Can\'t add Course to cart.', 'learnpress' ) );
367 + throw new Exception( esc_html__( 'Error: The course cannot be added to the cart.', 'learnpress' ) );
348 368 }
349 369
350 370 if ( is_user_logged_in() ) {
351 371 $order_id = $checkout->create_order();
352 -
353 - if ( is_wp_error( $order_id ) ) {
354 - throw new Exception( $order_id->get_error_message() );
355 - }
356 -
357 - $order = new LP_Order( $order_id );
358 -
372 + $order = new LP_Order( $order_id );
359 373 $order->payment_complete();
360 -
361 374 $cart->empty_cart();
362 375 }
363 376
364 377 $response->status = 'success';
@@ -363,8 +376,9 @@
363 376
364 377 $response->status = 'success';
365 378 $response->message = esc_html__( 'Verify Receipt Data successfully.', 'learnpress' );
366 379 } catch ( \Throwable $th ) {
380 + error_log( $th->getMessage() );
367 381 $response->status = 'error';
368 382 $response->message = $th->getMessage();
369 383 }
370 384
@@ -370,8 +384,241 @@
370 384
371 385 return rest_ensure_response( $response );
372 386 }
373 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 +
374 621 public function enroll_course( $request ) {
375 622 if ( ! class_exists( 'LP_REST_Courses_Controller' ) ) {
376 623 include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-courses-controller.php';
377 624 }
@@ -395,9 +642,9 @@
395 642 $course = learn_press_get_course( $course_id );
396 643 $check = $user->can_show_finish_course_btn( $course );
397 644
398 645 if ( $check['status'] !== 'success' ) {
399 - throw new Exception( $check['message'] ?? esc_html__( 'Can not finish course.', 'learnpress' ) );
646 + throw new Exception( $check['message'] ?? esc_html__( 'Cannot finish this course.', 'learnpress' ) );
400 647 }
401 648
402 649 $finished = $user->finish_course( $course_id );
403 650
@@ -417,15 +664,20 @@
417 664
418 665 public function retake_course( $request ) {
419 666 $response = new LP_REST_Response();
420 667
421 - if ( ! class_exists( 'LP_REST_Courses_Controller' ) ) {
422 - include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-courses-controller.php';
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();
423 677 }
424 678
425 - $course_controller = new LP_REST_Courses_Controller();
426 -
427 - return $course_controller->retake_course( $request );
679 + return $response;
428 680 }
429 681
430 682 /**
431 683 * Create a single product.
@@ -430,8 +682,9 @@
430 682 /**
431 683 * Create a single product.
432 684 *
433 685 * @param WP_REST_Request $request Full details about the request.
686 + *
434 687 * @return WP_Error|WP_REST_Response
435 688 */
436 689 public function create_item( $request ) {
437 690 if ( ! empty( $request['id'] ) ) {
@@ -488,8 +741,13 @@
488 741 public function prepare_object_for_response( $object, $request ) {
489 742 $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
490 743 $data = $this->get_course_data( $object, $context, $request );
491 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 +
492 750 $response = rest_ensure_response( $data );
493 751
494 752 return apply_filters( "lp_jwt_rest_prepare_{$this->post_type}_object", $response, $object, $request );
495 753 }
@@ -494,8 +752,10 @@
494 752 return apply_filters( "lp_jwt_rest_prepare_{$this->post_type}_object", $response, $object, $request );
495 753 }
496 754
497 755 /**
756 + * @param LP_Course $course
757 + *
498 758 * @throws Exception
499 759 */
500 760 protected function get_course_data( $course, $context = 'view' ) {
501 761 $request = func_num_args() >= 2 ? func_get_arg( 2 ) : new WP_REST_Request( '', '', array( 'context' => $context ) );
@@ -580,9 +840,9 @@
580 840 case 'tags':
581 841 $data['tags'] = $this->get_course_taxonomy( $id, 'course_tag' );
582 842 break;
583 843 case 'instructor':
584 - $data['instructor'] = $this->get_instructor_info( $id, $request, $course );
844 + $data['instructor'] = $this->get_instructor_info( $course );
585 845 break;
586 846 case 'sections':
587 847 $data['sections'] = $this->get_all_items( $course );
588 848 break;
@@ -617,8 +877,34 @@
617 877
618 878 return $data;
619 879 }
620 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 +
621 907 public function get_retaken_count( $id ) {
622 908 $user_id = get_current_user_id();
623 909
624 910 if ( ! $user_id ) {
@@ -631,9 +917,8 @@
631 917 return 0;
632 918 }
633 919
634 920 $user_course_data = $user->get_course_data( $id );
635 -
636 921 if ( ! $user_course_data ) {
637 922 return 0;
638 923 }
639 924
@@ -693,30 +978,89 @@
693 978
694 979 return false;
695 980 }
696 981
697 - public function get_instructor_info( $id, $request, $course ) {
698 - $user_id = get_post_meta( $id, '_lp_course_author', true );
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 + }
699 993
700 - $output = array();
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 + }
701 1001
702 - $extra_info = learn_press_get_user_extra_profile_info( $user_id );
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 + }
703 1008
704 - $instructor = $course->get_instructor();
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 );
705 1015
706 - $output['avatar'] = $instructor->get_upload_profile_src();
1016 + return $output;
1017 + }
707 1018
708 - if ( $user_id ) {
709 - $user = get_user_by( 'ID', absint( $user_id ) );
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();
710 1031
711 - if ( $user ) {
712 - $output['id'] = absint( $user_id );
713 - $output['name'] = $user->display_name;
714 - $output['description'] = $user->description;
715 - $output['social'] = $extra_info;
716 - }
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 );
717 1044 }
718 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 +
719 1063 return $output;
720 1064 }
721 1065
722 1066 public function get_item_learned_ids( $request ) {
@@ -761,11 +1105,11 @@
761 1105
762 1106 return array(
763 1107 'graduation' => $course_data->get_graduation() ?? '',
764 1108 'status' => $course_data->get_status() ?? '',
765 - 'start_time' => $course_data->get_start_time() ? lp_jwt_prepare_date_response( $course_data->get_start_time()->toSql( false ) ) : null,
766 - 'end_time' => $course_data->get_end_time() ? lp_jwt_prepare_date_response( $course_data->get_end_time()->toSql( false ) ) : null,
767 - 'expiration_time' => $course_data->get_expiration_time() ? lp_jwt_prepare_date_response( $course_data->get_expiration_time()->toSql( false ) ) : '',
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() ) : '',
768 1112 'result' => $course_data->calculate_course_results(),
769 1113 );
770 1114 }
771 1115
@@ -788,8 +1132,10 @@
788 1132
789 1133 /**
790 1134 * Get Items of sections
791 1135 *
1136 + * @param LP_Course $course
1137 + *
792 1138 * @throws Exception
793 1139 * @editor tungnx
794 1140 * @modify 4.1.3
795 1141 * @version 4.0.1
@@ -839,14 +1185,15 @@
839 1185
840 1186 $data_item[] = array(
841 1187 'id' => $item->get_id(),
842 1188 'type' => $item->get_item_type(),
1189 + 'slug' => $post->post_name,
843 1190 'title' => $post->post_title,
844 1191 'preview' => $item->is_preview(),
845 1192 'duration' => $item->get_duration()->to_timer( $format, true ),
846 1193 'graduation' => $user_item ? $user_item->get_graduation() : '',
847 1194 'status' => $user_item ? $user_item->get_status() : '',
848 - 'locked' => isset( $can_view_item->flag ) ? ! $can_view_item->flag : true,
1195 + 'locked' => ! isset( $can_view_item->flag ) || ! $can_view_item->flag,
849 1196 );
850 1197 }
851 1198 }
852 1199
@@ -858,12 +1205,17 @@
858 1205
859 1206 return $output;
860 1207 }
861 1208
1209 + /**
1210 + * Get all settings of the course metabox.
1211 + *
1212 + * @param int $id Course ID.
1213 + *
1214 + * @return array
1215 + */
862 1216 public function get_course_meta( $id ) {
863 - $user_id = get_current_user_id();
864 -
865 - if ( ! $user_id ) {
1217 + if ( empty( absint( $id ) ) ) {
866 1218 return array();
867 1219 }
868 1220
869 1221 if ( ! class_exists( 'LP_Meta_Box' ) ) {
@@ -873,9 +1225,9 @@
873 1225 if ( ! class_exists( 'LP_Meta_Box_Course' ) ) {
874 1226 include_once LP_PLUGIN_PATH . 'inc/admin/views/meta-boxes/course/settings.php';
875 1227 }
876 1228
877 - $metabox = new LP_Meta_Box_Course();
1229 + $metabox = LP_Meta_Box_Course::instance();
878 1230
879 1231 $output = array();
880 1232
881 1233 foreach ( $metabox->metabox( $id ) as $key => $tab ) {
@@ -988,9 +1340,9 @@
988 1340 'title' => $this->post_type,
989 1341 'type' => 'object',
990 1342 'properties' => array(
991 1343 'id' => array(
992 - 'description' => __( 'Unique identifier for the resource.', 'learnpress' ),
1344 + 'description' => __( 'A unique identifier for the resource.', 'learnpress' ),
993 1345 'type' => 'integer',
994 1346 'context' => array( 'view', 'edit' ),
995 1347 'readonly' => true,
996 1348 ),
@@ -1045,9 +1397,9 @@
1045 1397 'context' => array( 'view', 'edit' ),
1046 1398 'readonly' => true,
1047 1399 ),
1048 1400 'on_sale' => array(
1049 - 'description' => __( 'Shows if the course is on sale.', 'learnpress' ),
1401 + 'description' => __( 'Display courses if they are on sale.', 'learnpress' ),
1050 1402 'type' => 'boolean',
1051 1403 'context' => array( 'view', 'edit' ),
1052 1404 'readonly' => true,
1053 1405 ),
@@ -1058,9 +1410,9 @@
1058 1410 'enum' => array_merge( array_keys( get_post_statuses() ), array( 'future' ) ),
1059 1411 'context' => array( 'view', 'edit' ),
1060 1412 ),
1061 1413 'content' => array(
1062 - 'description' => __( 'Content course.', 'learnpress' ),
1414 + 'description' => __( 'Course content.', 'learnpress' ),
1063 1415 'type' => 'string',
1064 1416 'context' => array( 'view', 'edit' ),
1065 1417 ),
1066 1418 'excerpt' => array(
@@ -1073,27 +1425,27 @@
1073 1425 'type' => 'string',
1074 1426 'context' => array( 'view' ),
1075 1427 ),
1076 1428 'count_students' => array(
1077 - 'description' => __( 'Count student enrolled', 'learnpress' ),
1429 + 'description' => __( 'Count the number of enrolled students.', 'learnpress' ),
1078 1430 'type' => 'integer',
1079 1431 'context' => array( 'view' ),
1080 1432 'readonly' => true,
1081 1433 ),
1082 1434 'can_finish' => array(
1083 - 'description' => __( 'Can finish course', 'learnpress' ),
1435 + 'description' => __( 'Can finish the course', 'learnpress' ),
1084 1436 'type' => 'boolean',
1085 1437 'context' => array( 'view' ),
1086 1438 'readonly' => true,
1087 1439 ),
1088 1440 'can_retake' => array(
1089 - 'description' => __( 'Can retake course', 'learnpress' ),
1441 + 'description' => __( 'Can retake the course', 'learnpress' ),
1090 1442 'type' => 'boolean',
1091 1443 'context' => array( 'view' ),
1092 1444 'readonly' => true,
1093 1445 ),
1094 1446 'ratake_count' => array(
1095 - 'description' => __( 'Total retake', 'learnpress' ),
1447 + 'description' => __( 'Total retakes', 'learnpress' ),
1096 1448 'type' => 'integer',
1097 1449 'context' => array( 'view' ),
1098 1450 'readonly' => true,
1099 1451 ),
@@ -1228,8 +1580,14 @@
1228 1580 'type' => 'array',
1229 1581 'context' => array( 'view' ),
1230 1582 'readonly' => true,
1231 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 + ),
1232 1590 ),
1233 1591 ),
1234 1592 ),
1235 1593 'sections' => array(
@@ -1273,8 +1631,13 @@
1273 1631 'description' => __( 'Item Type.', 'learnpress' ),
1274 1632 'type' => 'string',
1275 1633 'context' => array( 'view', 'edit' ),
1276 1634 ),
1635 + 'slug' => array(
1636 + 'description' => __( 'Item slug.', 'learnpress' ),
1637 + 'type' => 'string',
1638 + 'context' => array( 'view', 'edit' ),
1639 + ),
1277 1640 'title' => array(
1278 1641 'description' => __( 'Item title.', 'learnpress' ),
1279 1642 'type' => 'string',
1280 1643 'context' => array( 'view', 'edit' ),
@@ -1364,15 +1727,15 @@
1364 1727
1365 1728 $params['orderby']['enum'] = array_merge( $params['orderby']['enum'], array( 'menu_order', 'price' ) );
1366 1729
1367 1730 $params['category'] = array(
1368 - 'description' => 'Limit result set to courses assigned a specific category ID.',
1731 + 'description' => 'Limit the result set to courses assigned to a specific category ID.',
1369 1732 'type' => 'string',
1370 1733 'sanitize_callback' => 'wp_parse_id_list',
1371 1734 'validate_callback' => 'rest_validate_request_arg',
1372 1735 );
1373 1736 $params['tag'] = array(
1374 - 'description' => 'Limit result set to courses assigned a specific tag ID.',
1737 + 'description' => 'Limit the result set to courses assigned to a specific tag ID.',
1375 1738 'type' => 'string',
1376 1739 'sanitize_callback' => 'wp_parse_id_list',
1377 1740 'validate_callback' => 'rest_validate_request_arg',
1378 1741 );
@@ -1394,9 +1757,9 @@
1394 1757 'type' => 'boolean',
1395 1758 'validate_callback' => 'rest_validate_request_arg',
1396 1759 );
1397 1760 $params['optimize'] = array(
1398 - 'description' => 'Disable some fields schema.',
1761 + 'description' => 'Disable some fields in the schema.',
1399 1762 'type' => array( 'boolean', 'string' ),
1400 1763 'validate_callback' => 'wp_parse_id_list',
1401 1764 );
1402 1765