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
learnpress / inc / jwt / rest-api / version1 / class-lp-rest-courses-v1-controller.php

class-lp-rest-courses-v1-controller.php in LearnPress – WordPress LMS Plugin for Create and Sell Online Courses 4.4.9, at inc/jwt/rest-api/version1/class-lp-rest-courses-v1-controller.php

1,769 lines 56.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 }
1769