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-quiz-v1-controller.php +841 -751 4.3.8 → 4.4.10 View file →
@@ -1,751 +1,841 @@
1 -<?php
2 -class LP_Jwt_Quiz_V1_Controller extends LP_REST_Jwt_Posts_Controller {
3 - protected $namespace = 'learnpress/v1';
4 -
5 - protected $rest_base = 'quiz';
6 -
7 - protected $post_type = LP_QUIZ_CPT;
8 -
9 - protected $hierarchical = true;
10 -
11 - public function register_routes() {
12 - register_rest_route(
13 - $this->namespace,
14 - '/' . $this->rest_base,
15 - array(
16 - array(
17 - 'methods' => WP_REST_Server::READABLE,
18 - 'callback' => array( $this, 'get_items' ),
19 - 'permission_callback' => array( $this, 'get_items_permissions_check' ),
20 - 'args' => $this->get_collection_params(),
21 - ),
22 - 'schema' => array( $this, 'get_public_item_schema' ),
23 - )
24 - );
25 -
26 - register_rest_route(
27 - $this->namespace,
28 - '/' . $this->rest_base . '/(?P<id>[\d]+)',
29 - array(
30 - 'args' => array(
31 - 'id' => array(
32 - 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
33 - 'type' => 'integer',
34 - ),
35 - ),
36 - array(
37 - 'methods' => WP_REST_Server::READABLE,
38 - 'callback' => array( $this, 'get_item' ),
39 - 'permission_callback' => array( $this, 'get_item_permissions_check' ),
40 - 'args' => array(
41 - 'context' => $this->get_context_param(
42 - array(
43 - 'default' => 'view',
44 - )
45 - ),
46 - ),
47 - ),
48 - 'schema' => array( $this, 'get_public_item_schema' ),
49 - )
50 - );
51 -
52 - register_rest_route(
53 - $this->namespace,
54 - '/' . $this->rest_base . '/start',
55 - array(
56 - 'args' => array(
57 - 'id' => array(
58 - 'description' => esc_html__( 'Quiz ID.', 'learnpress' ),
59 - 'type' => 'integer',
60 - ),
61 - ),
62 - array(
63 - 'methods' => WP_REST_Server::CREATABLE,
64 - 'callback' => array( $this, 'start_quiz' ),
65 - 'permission_callback' => array( $this, 'get_item_permissions_check' ),
66 - ),
67 - )
68 - );
69 -
70 - register_rest_route(
71 - $this->namespace,
72 - '/' . $this->rest_base . '/check_answer',
73 - array(
74 - 'args' => array(
75 - 'id' => array(
76 - 'description' => esc_html__( 'Quiz ID.', 'learnpress' ),
77 - 'type' => 'integer',
78 - ),
79 - 'question_id' => array(
80 - 'description' => esc_html__( 'Question ID.', 'learnpress' ),
81 - 'type' => 'integer',
82 - ),
83 - ),
84 - array(
85 - 'methods' => WP_REST_Server::CREATABLE,
86 - 'callback' => array( $this, 'check_quiz' ),
87 - 'permission_callback' => array( $this, 'get_item_permissions_check' ),
88 - ),
89 - )
90 - );
91 -
92 - register_rest_route(
93 - $this->namespace,
94 - '/' . $this->rest_base . '/finish',
95 - array(
96 - 'args' => array(
97 - 'id' => array(
98 - 'description' => esc_html__( 'Quiz ID.', 'learnpress' ),
99 - 'type' => 'integer',
100 - ),
101 - 'answered' => array(
102 - 'description' => esc_html__( 'Answer all questions.', 'learnpress' ),
103 - 'type' => 'object',
104 - ),
105 - ),
106 - array(
107 - 'methods' => WP_REST_Server::CREATABLE,
108 - 'callback' => array( $this, 'submit_quiz' ),
109 - 'permission_callback' => array( $this, 'get_item_permissions_check' ),
110 - ),
111 - )
112 - );
113 - }
114 -
115 - /**
116 - * Checks if a course can be read.
117 - *
118 - * Correctly handles courses with the inherit status.
119 - *
120 - * @author Nhamdv
121 - *
122 - * @return bool Whether the post can be read.
123 - * */
124 - public function check_read_permission( $post_id ) {
125 - if ( empty( absint( $post_id ) ) ) {
126 - return false;
127 - }
128 -
129 - $post = get_post( $post_id );
130 -
131 - if ( ! $post ) {
132 - return false;
133 - }
134 -
135 - if ( lp_rest_check_post_permissions( $this->post_type, 'read', $post_id ) ) {
136 - return true;
137 - }
138 -
139 - $post_status_obj = get_post_status_object( $post->post_status );
140 - if ( ! $post_status_obj || ! $post_status_obj->public ) {
141 - return false;
142 - }
143 -
144 - $user_id = get_current_user_id();
145 -
146 - if ( ! $user_id ) {
147 - return false;
148 - }
149 -
150 - $user = learn_press_get_user( $user_id );
151 -
152 - // Get course ID by lesson ID assigned.
153 - $course_id = $this->get_course_by_item_id( $post_id );
154 -
155 - if ( empty( $course_id ) ) {
156 - return false;
157 - }
158 -
159 - $can_view_content_course = $user->can_view_content_course( $course_id );
160 -
161 - $can_view_item = $user->can_view_item( $post_id, $can_view_content_course );
162 -
163 - if ( ! $can_view_item->flag ) {
164 - return false;
165 - }
166 -
167 - // Can we read the parent if we're inheriting?
168 - if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) {
169 - $parent = get_post( $post->post_parent );
170 -
171 - if ( $parent ) {
172 - return $this->check_read_permission( $parent );
173 - }
174 - }
175 -
176 - return true;
177 - }
178 -
179 - protected function get_course_by_item_id( $item_id ) {
180 - static $output;
181 -
182 - global $wpdb;
183 -
184 - if ( empty( $item_id ) ) {
185 - return false;
186 - }
187 -
188 - if ( ! isset( $output ) ) {
189 - $output = $wpdb->get_var(
190 - $wpdb->prepare(
191 - "SELECT c.ID FROM {$wpdb->posts} c
192 - INNER JOIN {$wpdb->learnpress_sections} s ON c.ID = s.section_course_id
193 - INNER JOIN {$wpdb->learnpress_section_items} si ON si.section_id = s.section_id
194 - WHERE si.item_id = %d ORDER BY si.section_id DESC LIMIT 1
195 - ",
196 - $item_id
197 - )
198 - );
199 - }
200 -
201 - if ( $output ) {
202 - return absint( $output );
203 - }
204 -
205 - return false;
206 - }
207 -
208 - protected function get_object( $quiz = 0 ) {
209 - global $post;
210 -
211 - if ( false === $quiz && isset( $post, $post->ID ) && LP_QUIZ_CPT === get_post_type( $post->ID ) ) {
212 - $id = absint( $post->ID );
213 - } elseif ( is_numeric( $quiz ) ) {
214 - $id = $quiz;
215 - } elseif ( $quiz instanceof LP_Quiz ) {
216 - $id = $quiz->get_id();
217 - } elseif ( ! empty( $quiz->ID ) ) {
218 - $id = $quiz->ID;
219 - }
220 -
221 - return new LP_Quiz( $id );
222 - }
223 -
224 - public function start_quiz( $request ) {
225 - $response = new LP_REST_Response();
226 - $quiz_id = isset( $request['id'] ) ? absint( $request['id'] ) : '';
227 - $course_id = $this->get_course_by_item_id( $quiz_id );
228 -
229 - if ( empty( $quiz_id ) || empty( $course_id ) ) {
230 - $response->status = 'error';
231 - $response->message = esc_html__( 'There is no Quiz ID or Quiz assigned in the course.', 'learnpress' );
232 -
233 - return rest_ensure_response( $response );
234 - }
235 -
236 - if ( ! class_exists( 'LP_REST_Users_Controller' ) ) {
237 - include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-users-controller.php';
238 - }
239 -
240 - $controller = new LP_REST_Users_Controller();
241 -
242 - $request->set_param( 'course_id', $course_id );
243 - $request->set_param( 'item_id', $quiz_id );
244 -
245 - return $controller->start_quiz( $request );
246 - }
247 -
248 - public function check_quiz( $request ) {
249 - $response = new LP_REST_Response();
250 - $quiz_id = isset( $request['id'] ) ? absint( $request['id'] ) : '';
251 - $question_id = isset( $request['question_id'] ) ? absint( $request['question_id'] ) : '';
252 - $answered = isset( $request['answered'] ) ? wp_unslash( $request['answered'] ) : '';
253 - $course_id = $this->get_course_by_item_id( $quiz_id );
254 -
255 - if ( empty( $quiz_id ) || empty( $course_id ) ) {
256 - $response->status = 'error';
257 - $response->message = esc_html__( 'There is no Quiz ID or Quiz assigned in the course.', 'learnpress' );
258 -
259 - return rest_ensure_response( $response );
260 - }
261 -
262 - if ( ! class_exists( 'LP_REST_Users_Controller' ) ) {
263 - include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-users-controller.php';
264 - }
265 -
266 - $controller = new LP_REST_Users_Controller();
267 -
268 - $request->set_param( 'answered', $answered );
269 - $request->set_param( 'question_id', $question_id );
270 - $request->set_param( 'course_id', $course_id );
271 - $request->set_param( 'item_id', $quiz_id );
272 -
273 - return $controller->check_answer( $request );
274 - }
275 -
276 - public function submit_quiz( $request ) {
277 - $response = new LP_REST_Response();
278 - $quiz_id = isset( $request['id'] ) ? absint( $request['id'] ) : '';
279 - $answered = isset( $request['answered'] ) ? wp_unslash( $request['answered'] ) : '';
280 -
281 - $course_id = $this->get_course_by_item_id( $quiz_id );
282 -
283 - if ( empty( $quiz_id ) || empty( $course_id ) ) {
284 - $response->status = 'error';
285 - $response->message = esc_html__( 'There is no Quiz ID or Quiz assigned in the course.', 'learnpress' );
286 -
287 - return rest_ensure_response( $response );
288 - }
289 -
290 - if ( ! isset( $request['answered'] ) ) {
291 - $response->status = 'error';
292 - $response->message = esc_html__( 'No Answer param.', 'learnpress' );
293 -
294 - return rest_ensure_response( $response );
295 - }
296 -
297 - if ( ! class_exists( 'LP_REST_Users_Controller' ) ) {
298 - include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-users-controller.php';
299 - }
300 -
301 - $controller = new LP_REST_Users_Controller();
302 -
303 - $request->set_param( 'answered', $answered );
304 - $request->set_param( 'course_id', $course_id );
305 - $request->set_param( 'item_id', $quiz_id );
306 -
307 - return $controller->submit_quiz( $request );
308 - }
309 -
310 - public function prepare_object_for_response( $object, $request ) {
311 - $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
312 - $data = $this->get_quiz_data( $object, $context, $request );
313 -
314 - $response = rest_ensure_response( $data );
315 -
316 - return apply_filters( "lp_jwt_rest_prepare_{$this->post_type}_object", $response, $object, $request );
317 - }
318 -
319 - protected function get_quiz_data( $object, $context = 'view' ) {
320 - $request = func_num_args() >= 2 ? func_get_arg( 2 ) : new WP_REST_Request( '', '', array( 'context' => $context ) );
321 - $fields = $this->get_fields_for_response( $request );
322 -
323 - $id = ! empty( $object->ID ) ? $object->ID : $object->get_id();
324 - $post = get_post( $id );
325 - $data = array();
326 -
327 - $assigned = $this->get_assigned( $id );
328 - if ( ! empty( $assigned ) && method_exists( $object, 'set_course' ) ) {
329 - $course_id = $assigned['course']['id'];
330 - $object->set_course( $course_id );
331 - }
332 -
333 - foreach ( $fields as $field ) {
334 - switch ( $field ) {
335 - case 'id':
336 - $data['id'] = $id;
337 - break;
338 - case 'name':
339 - $data['name'] = $post->post_title;
340 - break;
341 - case 'slug':
342 - $data['slug'] = $post->post_name;
343 - break;
344 - case 'permalink':
345 - $data['permalink'] = $object->get_permalink();
346 - break;
347 - case 'date_created':
348 - $data['date_created'] = lp_jwt_prepare_date_response( $post->post_date_gmt, $post->post_date );
349 - break;
350 - case 'date_created_gmt':
351 - $data['date_created_gmt'] = lp_jwt_prepare_date_response( $post->post_date_gmt );
352 - break;
353 - case 'date_modified':
354 - $data['date_modified'] = lp_jwt_prepare_date_response( $post->post_modified_gmt, $post->post_modified );
355 - break;
356 - case 'date_modified_gmt':
357 - $data['date_modified_gmt'] = lp_jwt_prepare_date_response( $post->post_modified_gmt );
358 - break;
359 - case 'status':
360 - $data['status'] = $post->post_status;
361 - break;
362 - case 'content':
363 - $data['content'] = 'view' === $context ? apply_filters( 'the_content', $post->post_content ) : $post->post_content;
364 - break;
365 - case 'excerpt':
366 - $data['excerpt'] = $post->post_excerpt;
367 - break;
368 - case 'can_finish_course':
369 - $data['can_finish_course'] = $this->check_can_finish_course( $id );
370 - break;
371 - case 'duration':
372 - $data['duration'] = learn_press_get_post_translated_duration( $id, esc_html__( 'Lifetime', 'learnpress' ) );
373 - break;
374 - case 'assigned':
375 - $data['assigned'] = $assigned;
376 - break;
377 - case 'questions':
378 - $data['questions'] = $this->get_quiz_results( $object, true )['questions'] ?? array();
379 - break;
380 - case 'results':
381 - $data['results'] = $this->get_quiz_results( $object );
382 - break;
383 - }
384 - }
385 -
386 - $data['meta_data'] = $this->get_course_meta( $id );
387 -
388 - return $data;
389 - }
390 -
391 - public function check_can_finish_course( $id ) {
392 - $user = learn_press_get_current_user();
393 -
394 - if ( ! $user || ! $id ) {
395 - return false;
396 - }
397 -
398 - $course_id = $this->get_course_by_item_id( $id );
399 -
400 - if ( empty( $course_id ) ) {
401 - return false;
402 - }
403 -
404 - $course = learn_press_get_course( $course_id );
405 -
406 - if ( $user && $course ) {
407 - $check = $user->can_show_finish_course_btn( $course );
408 -
409 - if ( $check['status'] === 'success' ) {
410 - return true;
411 - }
412 -
413 - return false;
414 - }
415 -
416 - return false;
417 - }
418 -
419 - public function get_quiz_results( $quiz, $get_question = false ) {
420 - $output = array(
421 - 'status' => '',
422 - );
423 -
424 - $user = learn_press_get_current_user();
425 -
426 - if ( ! $user || ! $quiz ) {
427 - return $output;
428 - }
429 -
430 - $id = ! empty( $quiz->ID ) ? $quiz->ID : $quiz->get_id();
431 -
432 - $course_id = $this->get_course_by_item_id( $id );
433 -
434 - if ( empty( $course_id ) ) {
435 - return $output;
436 - }
437 -
438 - $user_course = $user->get_course_data( $course_id );
439 - $user_quiz = $user_course ? $user_course->get_item( $id ) : false;
440 - $show_check = $quiz->get_instant_check();
441 - $show_correct_review = $quiz->get_show_correct_review();
442 - $answered = array();
443 - $status = '';
444 - $checked_questions = array();
445 - $question_ids = $quiz->get_question_ids();
446 -
447 - if ( $user_quiz ) {
448 - $status = $user_quiz->get_status();
449 - $quiz_results = $user_quiz->get_results( '' );
450 - $checked_questions = $user_quiz->get_checked_questions();
451 - $expiration_time = $user_quiz->get_expiration_time();
452 -
453 - $output = array(
454 - 'status' => $status,
455 - 'attempts' => $user_quiz->get_attempts(),
456 - 'checked_questions' => $checked_questions,
457 - 'start_time' => lp_jwt_prepare_date_response( $user_quiz->get_start_time()->toSql() ),
458 - 'retaken' => absint( $user_quiz->get_retaken_count() ),
459 - );
460 -
461 - $output['total_time'] = $user_quiz->get_timestamp_remaining();
462 -
463 - if ( $quiz_results ) {
464 - $output['results'] = $quiz_results->get();
465 - $answered = $quiz_results->getQuestions();
466 - $question_ids = $quiz_results->getQuestions( 'ids' );
467 - }
468 -
469 - $output['answered'] = ! empty( $answered ) ? (object) $answered : new stdClass();
470 - $output['question_ids'] = array_map( 'absint', array_values( $question_ids ) );
471 - }
472 -
473 - $duration = $quiz->get_duration();
474 -
475 - $array = array(
476 - 'passing_grade' => $quiz->get_passing_grade(),
477 - 'negative_marking' => $quiz->get_negative_marking(),
478 - 'instant_check' => $quiz->get_instant_check(),
479 - 'retake_count' => (float) $quiz->get_retake_count(),
480 - 'questions_per_page' => $quiz->get_pagination(),
481 - 'page_numbers' => get_post_meta( $quiz->get_id(), '_lp_pagination_numbers', true ) === 'yes',
482 - 'review_questions' => $quiz->get_review_questions(),
483 - 'support_options' => learn_press_get_question_support_answer_options(),
484 - 'duration' => $duration ? $duration->get() : false,
485 - );
486 -
487 - if ( function_exists( 'learn_press_rest_prepare_user_questions' ) && $get_question ) {
488 - $questions = learn_press_rest_prepare_user_questions(
489 - $question_ids,
490 - array(
491 - 'instant_check' => $show_check,
492 - 'quiz_status' => $status,
493 - 'checked_questions' => $checked_questions,
494 - 'answered' => $answered,
495 - 'show_correct_review' => $show_correct_review,
496 - )
497 - );
498 -
499 - $output['questions'] = $questions;
500 - }
501 -
502 - return array_merge( $array, $output );
503 - }
504 -
505 - public function get_course_meta( $id ) {
506 - if ( ! class_exists( 'LP_Meta_Box_Quiz' ) ) {
507 - include_once LP_PLUGIN_PATH . 'inc/admin/views/meta-boxes/quiz/settings.php';
508 - }
509 -
510 - $metabox = new LP_Meta_Box_Quiz();
511 -
512 - $output = array();
513 - foreach ( $metabox->metabox( $id ) as $meta_key => $object ) {
514 - if ( is_a( $object, 'LP_Meta_Box_Field' ) ) {
515 - $object->id = $meta_key;
516 - $output[ $meta_key ] = $object->meta_value( $id );
517 - }
518 - }
519 -
520 - return $output;
521 - }
522 -
523 - public function get_assigned( int $id ) : array {
524 - $courses = learn_press_get_item_courses( $id );
525 -
526 - $output = array();
527 -
528 - if ( $courses ) {
529 - foreach ( $courses as $course ) {
530 - $output['course'] = array(
531 - 'id' => $course->ID,
532 - 'title' => $course->post_title,
533 - 'slug' => $course->post_name,
534 - 'content' => $course->post_content,
535 - 'author' => $course->post_author,
536 - );
537 - }
538 - }
539 -
540 - return $output;
541 - }
542 -
543 - public function get_all_question( object $quiz ) : array {
544 - $questions = array();
545 -
546 - if ( function_exists( 'learn_press_rest_prepare_user_questions' ) ) {
547 - $questions = learn_press_rest_prepare_user_questions(
548 - $quiz->get_question_ids(),
549 - array(
550 - 'instant_check' => $quiz->get_instant_check(),
551 - 'show_correct_review' => $quiz->get_show_correct_review(),
552 - )
553 - );
554 - }
555 -
556 - return $questions;
557 - }
558 -
559 - public function get_item_schema() {
560 - $schema = array(
561 - '$schema' => 'http://json-schema.org/draft-04/schema#',
562 - 'title' => $this->post_type,
563 - 'type' => 'object',
564 - 'properties' => array(
565 - 'id' => array(
566 - 'description' => __( 'A unique identifier for the resource.', 'learnpress' ),
567 - 'type' => 'integer',
568 - 'context' => array( 'view', 'edit' ),
569 - 'readonly' => true,
570 - ),
571 - 'name' => array(
572 - 'description' => __( 'Course name.', 'learnpress' ),
573 - 'type' => 'string',
574 - 'context' => array( 'view', 'edit' ),
575 - ),
576 - 'slug' => array(
577 - 'description' => __( 'Course slug.', 'learnpress' ),
578 - 'type' => 'string',
579 - 'context' => array( 'view', 'edit' ),
580 - ),
581 - 'permalink' => array(
582 - 'description' => __( 'Course URL.', 'learnpress' ),
583 - 'type' => 'string',
584 - 'format' => 'uri',
585 - 'context' => array( 'view', 'edit' ),
586 - 'readonly' => true,
587 - ),
588 - 'date_created' => array(
589 - 'description' => __( "The date the Course was created, in the site's timezone.", 'learnpress' ),
590 - 'type' => 'date-time',
591 - 'context' => array( 'view', 'edit' ),
592 - 'readonly' => true,
593 - ),
594 - 'date_created_gmt' => array(
595 - 'description' => __( 'The date the Course was created, as GMT.', 'learnpress' ),
596 - 'type' => 'date-time',
597 - 'context' => array( 'view', 'edit' ),
598 - 'readonly' => true,
599 - ),
600 - 'date_modified' => array(
601 - 'description' => __( "The date the Course was last modified, in the site's timezone.", 'learnpress' ),
602 - 'type' => 'date-time',
603 - 'context' => array( 'view', 'edit' ),
604 - 'readonly' => true,
605 - ),
606 - 'date_modified_gmt' => array(
607 - 'description' => __( 'The date the Course was last modified, as GMT.', 'learnpress' ),
608 - 'type' => 'date-time',
609 - 'context' => array( 'view', 'edit' ),
610 - 'readonly' => true,
611 - ),
612 - 'status' => array(
613 - 'description' => __( 'Course status (post status).', 'learnpress' ),
614 - 'type' => 'string',
615 - 'default' => 'publish',
616 - 'enum' => array_merge( array_keys( get_post_statuses() ), array( 'future' ) ),
617 - 'context' => array( 'view', 'edit' ),
618 - ),
619 - 'content' => array(
620 - 'description' => __( 'Course content.', 'learnpress' ),
621 - 'type' => 'string',
622 - 'context' => array( 'view', 'edit' ),
623 - ),
624 - 'excerpt' => array(
625 - 'description' => __( 'Retrieves the course excerpt..', 'learnpress' ),
626 - 'type' => 'string',
627 - 'context' => array( 'view', 'edit' ),
628 - ),
629 - 'can_finish_course' => array(
630 - 'description' => __( 'Can finish the course', 'learnpress' ),
631 - 'type' => 'boolean',
632 - 'context' => array( 'view' ),
633 - 'readonly' => true,
634 - ),
635 - 'duration' => array(
636 - 'description' => __( 'Duration', 'learnpress' ),
637 - 'type' => 'string',
638 - 'context' => array( 'view' ),
639 - ),
640 - 'assigned' => array(
641 - 'description' => __( 'Assigned.', 'learnpress' ),
642 - 'type' => 'array',
643 - 'context' => array( 'view', 'edit' ),
644 - 'items' => array(
645 - 'id' => array(
646 - 'description' => __( 'Item ID.', 'learnpress' ),
647 - 'type' => 'integer',
648 - 'context' => array( 'view', 'edit' ),
649 - ),
650 - 'title' => array(
651 - 'description' => __( 'Title.', 'learnpress' ),
652 - 'type' => 'string',
653 - 'context' => array( 'view', 'edit' ),
654 - ),
655 - 'slug' => array(
656 - 'description' => __( 'Item slug.', 'learnpress' ),
657 - 'type' => 'string',
658 - 'context' => array( 'view', 'edit' ),
659 - ),
660 - 'content' => array(
661 - 'description' => __( 'Item Content.', 'learnpress' ),
662 - 'type' => 'string',
663 - 'context' => array( 'view', 'edit' ),
664 - ),
665 - 'author' => array(
666 - 'description' => __( 'Item Author.', 'learnpress' ),
667 - 'type' => 'integer',
668 - 'context' => array( 'view', 'edit' ),
669 - ),
670 - ),
671 - ),
672 - 'questions' => array(
673 - 'description' => __( 'List all of the Quiz Questions.', 'learnpress' ),
674 - 'type' => 'array',
675 - 'context' => array( 'view', 'edit' ),
676 - 'items' => array(
677 - 'description' => __( 'Question items.', 'learnpress' ),
678 - 'type' => 'object',
679 - 'context' => array( 'view', 'edit' ),
680 - 'items' => array(
681 - 'id' => array(
682 - 'description' => __( 'Item ID.', 'learnpress' ),
683 - 'type' => 'integer',
684 - 'context' => array( 'view', 'edit' ),
685 - ),
686 - 'type' => array(
687 - 'description' => __( 'Item Type.', 'learnpress' ),
688 - 'type' => 'string',
689 - 'context' => array( 'view', 'edit' ),
690 - ),
691 - 'title' => array(
692 - 'description' => __( 'Item title.', 'learnpress' ),
693 - 'type' => 'string',
694 - 'context' => array( 'view', 'edit' ),
695 - ),
696 - 'content' => array(
697 - 'description' => __( 'Item Content.', 'learnpress' ),
698 - 'type' => 'string',
699 - 'context' => array( 'view', 'edit' ),
700 - ),
701 - 'point' => array(
702 - 'description' => __( 'Point.', 'learnpress' ),
703 - 'type' => 'integer',
704 - 'context' => array( 'view', 'edit' ),
705 - ),
706 - 'hint' => array(
707 - 'description' => __( 'Question Hint.', 'learnpress' ),
708 - 'type' => 'string',
709 - 'context' => array( 'view', 'edit' ),
710 - ),
711 - 'options' => array(
712 - 'description' => __( 'Question Options.', 'learnpress' ),
713 - 'type' => 'array',
714 - 'context' => array( 'view', 'edit' ),
715 - 'items' => array(
716 - 'description' => __( 'Question items.', 'learnpress' ),
717 - 'type' => 'object',
718 - 'context' => array( 'view', 'edit' ),
719 - 'items' => array(
720 - 'title' => array(
721 - 'description' => __( 'Item title.', 'learnpress' ),
722 - 'type' => 'string',
723 - 'context' => array( 'view', 'edit' ),
724 - ),
725 - 'value' => array(
726 - 'description' => __( 'Item value.', 'learnpress' ),
727 - 'type' => 'string',
728 - 'context' => array( 'view', 'edit' ),
729 - ),
730 - 'uid' => array(
731 - 'description' => __( 'Item id.', 'learnpress' ),
732 - 'type' => 'integer',
733 - 'context' => array( 'view', 'edit' ),
734 - ),
735 - ),
736 - ),
737 - ),
738 - ),
739 - ),
740 - ),
741 - 'results' => array(
742 - 'description' => __( 'Retrieves the quiz result..', 'learnpress' ),
743 - 'type' => 'array',
744 - 'context' => array( 'view', 'edit' ),
745 - ),
746 - ),
747 - );
748 -
749 - return $this->add_additional_fields_schema( $schema );
750 - }
751 -}
1 +<?php
2 +class LP_Jwt_Quiz_V1_Controller extends LP_REST_Jwt_Posts_Controller {
3 + protected $namespace = 'learnpress/v1';
4 +
5 + protected $rest_base = 'quiz';
6 +
7 + protected $post_type = LP_QUIZ_CPT;
8 +
9 + protected $hierarchical = true;
10 +
11 + public function register_routes() {
12 + register_rest_route(
13 + $this->namespace,
14 + '/' . $this->rest_base,
15 + array(
16 + array(
17 + 'methods' => WP_REST_Server::READABLE,
18 + 'callback' => array( $this, 'get_items' ),
19 + 'permission_callback' => array( $this, 'get_items_permissions_check' ),
20 + 'args' => $this->get_collection_params(),
21 + ),
22 + 'schema' => array( $this, 'get_public_item_schema' ),
23 + )
24 + );
25 +
26 + register_rest_route(
27 + $this->namespace,
28 + '/' . $this->rest_base . '/(?P<id>[\d]+)',
29 + array(
30 + 'args' => array(
31 + 'id' => array(
32 + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
33 + 'type' => 'integer',
34 + ),
35 + ),
36 + array(
37 + 'methods' => WP_REST_Server::READABLE,
38 + 'callback' => array( $this, 'get_item' ),
39 + 'permission_callback' => array( $this, 'get_item_permissions_check' ),
40 + 'args' => array(
41 + 'context' => $this->get_context_param(
42 + array(
43 + 'default' => 'view',
44 + )
45 + ),
46 + ),
47 + ),
48 + 'schema' => array( $this, 'get_public_item_schema' ),
49 + )
50 + );
51 +
52 + register_rest_route(
53 + $this->namespace,
54 + '/' . $this->rest_base . '/start',
55 + array(
56 + 'args' => array(
57 + 'id' => array(
58 + 'description' => esc_html__( 'Quiz ID.', 'learnpress' ),
59 + 'type' => 'integer',
60 + ),
61 + ),
62 + array(
63 + 'methods' => WP_REST_Server::CREATABLE,
64 + 'callback' => array( $this, 'start_quiz' ),
65 + 'permission_callback' => array( $this, 'get_item_permissions_check' ),
66 + ),
67 + )
68 + );
69 +
70 + register_rest_route(
71 + $this->namespace,
72 + '/' . $this->rest_base . '/check_answer',
73 + array(
74 + 'args' => array(
75 + 'id' => array(
76 + 'description' => esc_html__( 'Quiz ID.', 'learnpress' ),
77 + 'type' => 'integer',
78 + ),
79 + 'question_id' => array(
80 + 'description' => esc_html__( 'Question ID.', 'learnpress' ),
81 + 'type' => 'integer',
82 + ),
83 + ),
84 + array(
85 + 'methods' => WP_REST_Server::CREATABLE,
86 + 'callback' => array( $this, 'check_quiz' ),
87 + 'permission_callback' => array( $this, 'get_item_permissions_check' ),
88 + ),
89 + )
90 + );
91 +
92 + register_rest_route(
93 + $this->namespace,
94 + '/' . $this->rest_base . '/finish',
95 + array(
96 + 'args' => array(
97 + 'id' => array(
98 + 'description' => esc_html__( 'Quiz ID.', 'learnpress' ),
99 + 'type' => 'integer',
100 + ),
101 + 'answered' => array(
102 + 'description' => esc_html__( 'Answer all questions.', 'learnpress' ),
103 + 'type' => 'object',
104 + ),
105 + ),
106 + array(
107 + 'methods' => WP_REST_Server::CREATABLE,
108 + 'callback' => array( $this, 'submit_quiz' ),
109 + 'permission_callback' => array( $this, 'get_item_permissions_check' ),
110 + ),
111 + )
112 + );
113 + }
114 +
115 + /**
116 + * Allow guests/unauthenticated users to query the list endpoint (filtered by slug).
117 + * Per-item visibility is still enforced by check_read_permission, which only
118 + * allows preview items for guests.
119 + */
120 + public function get_items_permissions_check( $request ) {
121 + return true;
122 + }
123 +
124 + /**
125 + * Checks if a course can be read.
126 + *
127 + * Correctly handles courses with the inherit status.
128 + *
129 + * @author Nhamdv
130 + *
131 + * @return bool Whether the post can be read.
132 + * */
133 + public function check_read_permission( $post_id ) {
134 + if ( empty( absint( $post_id ) ) ) {
135 + return false;
136 + }
137 +
138 + $post = get_post( $post_id );
139 +
140 + if ( ! $post ) {
141 + return false;
142 + }
143 +
144 + if ( lp_rest_check_post_permissions( $this->post_type, 'read', $post_id ) ) {
145 + return true;
146 + }
147 +
148 + $post_status_obj = get_post_status_object( $post->post_status );
149 + if ( ! $post_status_obj || ! $post_status_obj->public ) {
150 + return false;
151 + }
152 +
153 + // Allow guests and unenrolled users to read items marked as preview.
154 + $course_id = $this->get_course_by_item_id( $post_id );
155 + if ( $course_id ) {
156 + $course = learn_press_get_course( $course_id );
157 + if ( $course ) {
158 + $item = $course->get_item( $post_id );
159 + if ( $item && $item->is_preview() ) {
160 + return true;
161 + }
162 + }
163 + }
164 +
165 + $user_id = get_current_user_id();
166 +
167 + if ( ! $user_id ) {
168 + return false;
169 + }
170 +
171 + $user = learn_press_get_user( $user_id );
172 +
173 + if ( empty( $course_id ) ) {
174 + return false;
175 + }
176 +
177 + $can_view_content_course = $user->can_view_content_course( $course_id );
178 +
179 + $can_view_item = $user->can_view_item( $post_id, $can_view_content_course );
180 +
181 + if ( ! $can_view_item->flag ) {
182 + return false;
183 + }
184 +
185 + // Can we read the parent if we're inheriting?
186 + if ( 'inherit' === $post->post_status && $post->post_parent > 0 ) {
187 + $parent = get_post( $post->post_parent );
188 +
189 + if ( $parent ) {
190 + return $this->check_read_permission( $parent );
191 + }
192 + }
193 +
194 + return true;
195 + }
196 +
197 + protected function get_course_by_item_id( $item_id ) {
198 + static $output;
199 +
200 + global $wpdb;
201 +
202 + if ( empty( $item_id ) ) {
203 + return false;
204 + }
205 +
206 + if ( ! isset( $output ) ) {
207 + $output = $wpdb->get_var(
208 + $wpdb->prepare(
209 + "SELECT c.ID FROM {$wpdb->posts} c
210 + INNER JOIN {$wpdb->learnpress_sections} s ON c.ID = s.section_course_id
211 + INNER JOIN {$wpdb->learnpress_section_items} si ON si.section_id = s.section_id
212 + WHERE si.item_id = %d ORDER BY si.section_id DESC LIMIT 1
213 + ",
214 + $item_id
215 + )
216 + );
217 + }
218 +
219 + if ( $output ) {
220 + return absint( $output );
221 + }
222 +
223 + return false;
224 + }
225 +
226 + protected function get_object( $quiz = 0 ) {
227 + global $post;
228 +
229 + if ( false === $quiz && isset( $post, $post->ID ) && LP_QUIZ_CPT === get_post_type( $post->ID ) ) {
230 + $id = absint( $post->ID );
231 + } elseif ( is_numeric( $quiz ) ) {
232 + $id = $quiz;
233 + } elseif ( $quiz instanceof LP_Quiz ) {
234 + $id = $quiz->get_id();
235 + } elseif ( ! empty( $quiz->ID ) ) {
236 + $id = $quiz->ID;
237 + }
238 +
239 + return new LP_Quiz( $id );
240 + }
241 +
242 + public function start_quiz( $request ) {
243 + $response = new LP_REST_Response();
244 + $quiz_id = isset( $request['id'] ) ? absint( $request['id'] ) : '';
245 + $course_id = $this->get_course_by_item_id( $quiz_id );
246 +
247 + if ( empty( $quiz_id ) || empty( $course_id ) ) {
248 + $response->status = 'error';
249 + $response->message = esc_html__( 'There is no Quiz ID or Quiz assigned in the course.', 'learnpress' );
250 +
251 + return rest_ensure_response( $response );
252 + }
253 +
254 + if ( ! class_exists( 'LP_REST_Users_Controller' ) ) {
255 + include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-users-controller.php';
256 + }
257 +
258 + $controller = new LP_REST_Users_Controller();
259 +
260 + $request->set_param( 'course_id', $course_id );
261 + $request->set_param( 'item_id', $quiz_id );
262 +
263 + return $controller->start_quiz( $request );
264 + }
265 +
266 + public function check_quiz( $request ) {
267 + $response = new LP_REST_Response();
268 + $quiz_id = isset( $request['id'] ) ? absint( $request['id'] ) : '';
269 + $question_id = isset( $request['question_id'] ) ? absint( $request['question_id'] ) : '';
270 + $answered = isset( $request['answered'] ) ? wp_unslash( $request['answered'] ) : '';
271 + $course_id = $this->get_course_by_item_id( $quiz_id );
272 +
273 + if ( empty( $quiz_id ) || empty( $course_id ) ) {
274 + $response->status = 'error';
275 + $response->message = esc_html__( 'There is no Quiz ID or Quiz assigned in the course.', 'learnpress' );
276 +
277 + return rest_ensure_response( $response );
278 + }
279 +
280 + if ( ! class_exists( 'LP_REST_Users_Controller' ) ) {
281 + include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-users-controller.php';
282 + }
283 +
284 + $controller = new LP_REST_Users_Controller();
285 +
286 + $request->set_param( 'answered', $answered );
287 + $request->set_param( 'question_id', $question_id );
288 + $request->set_param( 'course_id', $course_id );
289 + $request->set_param( 'item_id', $quiz_id );
290 +
291 + return $controller->check_answer( $request );
292 + }
293 +
294 + public function submit_quiz( $request ) {
295 + $response = new LP_REST_Response();
296 + $quiz_id = isset( $request['id'] ) ? absint( $request['id'] ) : '';
297 + $answered = isset( $request['answered'] ) ? wp_unslash( $request['answered'] ) : '';
298 +
299 + $course_id = $this->get_course_by_item_id( $quiz_id );
300 +
301 + if ( empty( $quiz_id ) || empty( $course_id ) ) {
302 + $response->status = 'error';
303 + $response->message = esc_html__( 'There is no Quiz ID or Quiz assigned in the course.', 'learnpress' );
304 +
305 + return rest_ensure_response( $response );
306 + }
307 +
308 + if ( ! isset( $request['answered'] ) ) {
309 + $response->status = 'error';
310 + $response->message = esc_html__( 'No Answer param.', 'learnpress' );
311 +
312 + return rest_ensure_response( $response );
313 + }
314 +
315 + if ( ! class_exists( 'LP_REST_Users_Controller' ) ) {
316 + include_once LP_PLUGIN_PATH . 'inc/rest-api/v1/frontend/class-lp-rest-users-controller.php';
317 + }
318 +
319 + $controller = new LP_REST_Users_Controller();
320 +
321 + $request->set_param( 'answered', $answered );
322 + $request->set_param( 'course_id', $course_id );
323 + $request->set_param( 'item_id', $quiz_id );
324 +
325 + return $controller->submit_quiz( $request );
326 + }
327 +
328 + public function prepare_object_for_response( $object, $request ) {
329 + $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
330 + $data = $this->get_quiz_data( $object, $context, $request );
331 +
332 + $response = rest_ensure_response( $data );
333 +
334 + return apply_filters( "lp_jwt_rest_prepare_{$this->post_type}_object", $response, $object, $request );
335 + }
336 +
337 + protected function get_quiz_data( $object, $context = 'view' ) {
338 + $request = func_num_args() >= 2 ? func_get_arg( 2 ) : new WP_REST_Request( '', '', array( 'context' => $context ) );
339 + $fields = $this->get_fields_for_response( $request );
340 +
341 + $id = ! empty( $object->ID ) ? $object->ID : $object->get_id();
342 + $post = get_post( $id );
343 + $data = array();
344 +
345 + $assigned = $this->get_assigned( $id );
346 + if ( ! empty( $assigned ) && method_exists( $object, 'set_course' ) ) {
347 + $course_id = $assigned['course']['id'];
348 + $object->set_course( $course_id );
349 + }
350 +
351 + foreach ( $fields as $field ) {
352 + switch ( $field ) {
353 + case 'id':
354 + $data['id'] = $id;
355 + break;
356 + case 'name':
357 + $data['name'] = $post->post_title;
358 + break;
359 + case 'slug':
360 + $data['slug'] = $post->post_name;
361 + break;
362 + case 'permalink':
363 + $data['permalink'] = $object->get_permalink();
364 + break;
365 + case 'date_created':
366 + $data['date_created'] = lp_jwt_prepare_date_response( $post->post_date_gmt, $post->post_date );
367 + break;
368 + case 'date_created_gmt':
369 + $data['date_created_gmt'] = lp_jwt_prepare_date_response( $post->post_date_gmt );
370 + break;
371 + case 'date_modified':
372 + $data['date_modified'] = lp_jwt_prepare_date_response( $post->post_modified_gmt, $post->post_modified );
373 + break;
374 + case 'date_modified_gmt':
375 + $data['date_modified_gmt'] = lp_jwt_prepare_date_response( $post->post_modified_gmt );
376 + break;
377 + case 'status':
378 + $data['status'] = $post->post_status;
379 + break;
380 + case 'content':
381 + $data['content'] = 'view' === $context ? apply_filters( 'the_content', $post->post_content ) : $post->post_content;
382 + break;
383 + case 'excerpt':
384 + $data['excerpt'] = $post->post_excerpt;
385 + break;
386 + case 'can_finish_course':
387 + $data['can_finish_course'] = $this->check_can_finish_course( $id );
388 + break;
389 + case 'duration':
390 + $data['duration'] = learn_press_get_post_translated_duration( $id, esc_html__( 'Lifetime', 'learnpress' ) );
391 + break;
392 + case 'assigned':
393 + $data['assigned'] = $assigned;
394 + break;
395 + case 'questions':
396 + $data['questions'] = $this->get_quiz_results( $object, true )['questions'] ?? array();
397 + break;
398 + case 'results':
399 + $data['results'] = $this->get_quiz_results( $object );
400 + break;
401 + }
402 + }
403 +
404 + $data['meta_data'] = $this->get_course_meta( $id );
405 +
406 + return $data;
407 + }
408 +
409 + public function check_can_finish_course( $id ) {
410 + $user = learn_press_get_current_user();
411 +
412 + if ( ! $user || ! $id ) {
413 + return false;
414 + }
415 +
416 + $course_id = $this->get_course_by_item_id( $id );
417 +
418 + if ( empty( $course_id ) ) {
419 + return false;
420 + }
421 +
422 + $course = learn_press_get_course( $course_id );
423 +
424 + if ( $user && $course ) {
425 + $check = $user->can_show_finish_course_btn( $course );
426 +
427 + if ( $check['status'] === 'success' ) {
428 + return true;
429 + }
430 +
431 + return false;
432 + }
433 +
434 + return false;
435 + }
436 +
437 + public function get_quiz_results( $quiz, $get_question = false ) {
438 + $output = array(
439 + 'status' => '',
440 + );
441 +
442 + $user = learn_press_get_current_user();
443 +
444 + if ( ! $user || ! $quiz ) {
445 + return $output;
446 + }
447 +
448 + $id = ! empty( $quiz->ID ) ? $quiz->ID : $quiz->get_id();
449 +
450 + $course_id = $this->get_course_by_item_id( $id );
451 +
452 + if ( empty( $course_id ) ) {
453 + return $output;
454 + }
455 +
456 + $user_course = $user->get_course_data( $course_id );
457 + $user_quiz = $user_course ? $user_course->get_item( $id ) : false;
458 + $show_check = $quiz->get_instant_check();
459 + $show_correct_review = $quiz->get_show_correct_review();
460 + $answered = array();
461 + $status = '';
462 + $checked_questions = array();
463 + $question_ids = $quiz->get_question_ids();
464 +
465 + if ( $user_quiz ) {
466 + $status = $user_quiz->get_status();
467 + $quiz_results = $user_quiz->get_results( '' );
468 + $checked_questions = $user_quiz->get_checked_questions();
469 + $expiration_time = $user_quiz->get_expiration_time();
470 +
471 + $output = array(
472 + 'status' => $status,
473 + 'attempts' => $user_quiz->get_attempts(),
474 + 'checked_questions' => $checked_questions,
475 + 'start_time' => lp_jwt_prepare_date_response( $user_quiz->get_start_time()->toSql() ),
476 + 'retaken' => absint( $user_quiz->get_retaken_count() ),
477 + );
478 +
479 + $output['total_time'] = $user_quiz->get_timestamp_remaining();
480 +
481 + if ( $quiz_results ) {
482 + $output['results'] = $quiz_results->get();
483 + $answered = $quiz_results->getQuestions();
484 + $question_ids = $quiz_results->getQuestions( 'ids' );
485 + }
486 +
487 + $output['answered'] = ! empty( $answered ) ? (object) $answered : new stdClass();
488 + $output['question_ids'] = array_map( 'absint', array_values( $question_ids ) );
489 + }
490 +
491 + $duration = $quiz->get_duration();
492 +
493 + // Plugin's get_pagination() returns 0 when value <= 1 (treats 1 as "no pagination").
494 + // Override here so admins can set 1 question per page for step-by-step quizzes.
495 + $pagination_raw = absint( get_post_meta( $quiz->get_id(), '_lp_pagination', true ) );
496 +
497 + $array = array(
498 + 'passing_grade' => $quiz->get_passing_grade(),
499 + 'negative_marking' => $quiz->get_negative_marking(),
500 + 'instant_check' => $quiz->get_instant_check(),
501 + 'retake_count' => (float) $quiz->get_retake_count(),
502 + 'questions_per_page' => $pagination_raw > 0 ? $pagination_raw : 0,
503 + 'page_numbers' => get_post_meta( $quiz->get_id(), '_lp_pagination_numbers', true ) === 'yes',
504 + 'review_questions' => $quiz->get_review_questions(),
505 + 'support_options' => learn_press_get_question_support_answer_options(),
506 + 'duration' => $duration ? $duration->get() : false,
507 + );
508 +
509 + if ( function_exists( 'learn_press_rest_prepare_user_questions' ) && $get_question ) {
510 + $questions = learn_press_rest_prepare_user_questions(
511 + $question_ids,
512 + array(
513 + 'instant_check' => $show_check,
514 + 'quiz_status' => $status,
515 + 'checked_questions' => $checked_questions,
516 + 'answered' => $answered,
517 + 'show_correct_review' => $show_correct_review,
518 + )
519 + );
520 +
521 + // Post-process FIB questions: the actual question content (with [fib] shortcodes)
522 + // is stored in lp_question_answers.title — NOT post_content. Read it from there,
523 + // strip `fill=` answers, replace shortcodes with {{FIB_<id>}} placeholders for
524 + // the client. On reveal, attach `fib_answers` with per-blank correctness.
525 + foreach ( $questions as &$q ) {
526 + if ( ! isset( $q['type'] ) || $q['type'] !== 'fill_in_blanks' ) {
527 + continue;
528 + }
529 +
530 + $question_obj = $q['object'] ?? null;
531 + if ( ! $question_obj || ! method_exists( $question_obj, 'get_data' ) ) {
532 + continue;
533 + }
534 +
535 + $question_id = $q['id'] ?? 0;
536 + $answer_options = $question_obj->get_data( 'answer_options' );
537 + if ( empty( $answer_options ) || ! is_array( $answer_options ) ) {
538 + continue;
539 + }
540 +
541 + $first_answer = reset( $answer_options );
542 + $raw_content = $first_answer['title'] ?? '';
543 + $answer_id = $first_answer['question_answer_id'] ?? 0;
544 + $user_answer = $answered[ $question_id ]['answered'] ?? '';
545 + $revealed = $show_check || $status === 'completed';
546 +
547 + $q['content'] = apply_filters(
548 + 'learn-press/question/fib/regex-content',
549 + $raw_content,
550 + $answer_id,
551 + $revealed,
552 + $user_answer
553 + );
554 +
555 + // FIB has no answer "options" in radio/checkbox sense; clear them.
556 + $q['options'] = array();
557 +
558 + if ( $revealed && method_exists( $question_obj, 'get_answer_data' ) ) {
559 + $q['fib_answers'] = $question_obj->get_answer_data(
560 + $raw_content,
561 + $answer_id,
562 + $user_answer
563 + );
564 + }
565 + }
566 + unset( $q );
567 +
568 + // Plugin's `learn_press_rest_prepare_user_questions()` only emits `explanation`
569 + // when the user explicitly triggered instant_check on a question (it reads
570 + // `$args['status']` which is never set in the defaults). For completed quizzes
571 + // fill in explanations here so clients can render them on the review screen.
572 + if ( $status === 'completed' ) {
573 + foreach ( $questions as &$q ) {
574 + if ( ! empty( $q['explanation'] ) ) {
575 + continue;
576 + }
577 + $question_obj = $q['object'] ?? null;
578 + if ( $question_obj && method_exists( $question_obj, 'get_explanation' ) ) {
579 + $explanation = $question_obj->get_explanation();
580 + if ( $explanation ) {
581 + $q['explanation'] = $explanation;
582 + $q['has_explanation'] = true;
583 + }
584 + }
585 + }
586 + unset( $q );
587 + }
588 +
589 + $output['questions'] = $questions;
590 + }
591 +
592 + return array_merge( $array, $output );
593 + }
594 +
595 + public function get_course_meta( $id ) {
596 + if ( ! class_exists( 'LP_Meta_Box_Quiz' ) ) {
597 + include_once LP_PLUGIN_PATH . 'inc/admin/views/meta-boxes/quiz/settings.php';
598 + }
599 +
600 + $metabox = new LP_Meta_Box_Quiz();
601 +
602 + $output = array();
603 + foreach ( $metabox->metabox( $id ) as $meta_key => $object ) {
604 + if ( is_a( $object, 'LP_Meta_Box_Field' ) ) {
605 + $object->id = $meta_key;
606 + $output[ $meta_key ] = $object->meta_value( $id );
607 + }
608 + }
609 +
610 + return $output;
611 + }
612 +
613 + public function get_assigned( int $id ) : array {
614 + $courses = learn_press_get_item_courses( $id );
615 +
616 + $output = array();
617 +
618 + if ( $courses ) {
619 + foreach ( $courses as $course ) {
620 + $output['course'] = array(
621 + 'id' => $course->ID,
622 + 'title' => $course->post_title,
623 + 'slug' => $course->post_name,
624 + 'content' => $course->post_content,
625 + 'author' => $course->post_author,
626 + );
627 + }
628 + }
629 +
630 + return $output;
631 + }
632 +
633 + public function get_all_question( object $quiz ) : array {
634 + $questions = array();
635 +
636 + if ( function_exists( 'learn_press_rest_prepare_user_questions' ) ) {
637 + $questions = learn_press_rest_prepare_user_questions(
638 + $quiz->get_question_ids(),
639 + array(
640 + 'instant_check' => $quiz->get_instant_check(),
641 + 'show_correct_review' => $quiz->get_show_correct_review(),
642 + )
643 + );
644 + }
645 +
646 + return $questions;
647 + }
648 +
649 + public function get_item_schema() {
650 + $schema = array(
651 + '$schema' => 'http://json-schema.org/draft-04/schema#',
652 + 'title' => $this->post_type,
653 + 'type' => 'object',
654 + 'properties' => array(
655 + 'id' => array(
656 + 'description' => __( 'A unique identifier for the resource.', 'learnpress' ),
657 + 'type' => 'integer',
658 + 'context' => array( 'view', 'edit' ),
659 + 'readonly' => true,
660 + ),
661 + 'name' => array(
662 + 'description' => __( 'Course name.', 'learnpress' ),
663 + 'type' => 'string',
664 + 'context' => array( 'view', 'edit' ),
665 + ),
666 + 'slug' => array(
667 + 'description' => __( 'Course slug.', 'learnpress' ),
668 + 'type' => 'string',
669 + 'context' => array( 'view', 'edit' ),
670 + ),
671 + 'permalink' => array(
672 + 'description' => __( 'Course URL.', 'learnpress' ),
673 + 'type' => 'string',
674 + 'format' => 'uri',
675 + 'context' => array( 'view', 'edit' ),
676 + 'readonly' => true,
677 + ),
678 + 'date_created' => array(
679 + 'description' => __( "The date the Course was created, in the site's timezone.", 'learnpress' ),
680 + 'type' => 'date-time',
681 + 'context' => array( 'view', 'edit' ),
682 + 'readonly' => true,
683 + ),
684 + 'date_created_gmt' => array(
685 + 'description' => __( 'The date the Course was created, as GMT.', 'learnpress' ),
686 + 'type' => 'date-time',
687 + 'context' => array( 'view', 'edit' ),
688 + 'readonly' => true,
689 + ),
690 + 'date_modified' => array(
691 + 'description' => __( "The date the Course was last modified, in the site's timezone.", 'learnpress' ),
692 + 'type' => 'date-time',
693 + 'context' => array( 'view', 'edit' ),
694 + 'readonly' => true,
695 + ),
696 + 'date_modified_gmt' => array(
697 + 'description' => __( 'The date the Course was last modified, as GMT.', 'learnpress' ),
698 + 'type' => 'date-time',
699 + 'context' => array( 'view', 'edit' ),
700 + 'readonly' => true,
701 + ),
702 + 'status' => array(
703 + 'description' => __( 'Course status (post status).', 'learnpress' ),
704 + 'type' => 'string',
705 + 'default' => 'publish',
706 + 'enum' => array_merge( array_keys( get_post_statuses() ), array( 'future' ) ),
707 + 'context' => array( 'view', 'edit' ),
708 + ),
709 + 'content' => array(
710 + 'description' => __( 'Course content.', 'learnpress' ),
711 + 'type' => 'string',
712 + 'context' => array( 'view', 'edit' ),
713 + ),
714 + 'excerpt' => array(
715 + 'description' => __( 'Retrieves the course excerpt..', 'learnpress' ),
716 + 'type' => 'string',
717 + 'context' => array( 'view', 'edit' ),
718 + ),
719 + 'can_finish_course' => array(
720 + 'description' => __( 'Can finish the course', 'learnpress' ),
721 + 'type' => 'boolean',
722 + 'context' => array( 'view' ),
723 + 'readonly' => true,
724 + ),
725 + 'duration' => array(
726 + 'description' => __( 'Duration', 'learnpress' ),
727 + 'type' => 'string',
728 + 'context' => array( 'view' ),
729 + ),
730 + 'assigned' => array(
731 + 'description' => __( 'Assigned.', 'learnpress' ),
732 + 'type' => 'array',
733 + 'context' => array( 'view', 'edit' ),
734 + 'items' => array(
735 + 'id' => array(
736 + 'description' => __( 'Item ID.', 'learnpress' ),
737 + 'type' => 'integer',
738 + 'context' => array( 'view', 'edit' ),
739 + ),
740 + 'title' => array(
741 + 'description' => __( 'Title.', 'learnpress' ),
742 + 'type' => 'string',
743 + 'context' => array( 'view', 'edit' ),
744 + ),
745 + 'slug' => array(
746 + 'description' => __( 'Item slug.', 'learnpress' ),
747 + 'type' => 'string',
748 + 'context' => array( 'view', 'edit' ),
749 + ),
750 + 'content' => array(
751 + 'description' => __( 'Item Content.', 'learnpress' ),
752 + 'type' => 'string',
753 + 'context' => array( 'view', 'edit' ),
754 + ),
755 + 'author' => array(
756 + 'description' => __( 'Item Author.', 'learnpress' ),
757 + 'type' => 'integer',
758 + 'context' => array( 'view', 'edit' ),
759 + ),
760 + ),
761 + ),
762 + 'questions' => array(
763 + 'description' => __( 'List all of the Quiz Questions.', 'learnpress' ),
764 + 'type' => 'array',
765 + 'context' => array( 'view', 'edit' ),
766 + 'items' => array(
767 + 'description' => __( 'Question items.', 'learnpress' ),
768 + 'type' => 'object',
769 + 'context' => array( 'view', 'edit' ),
770 + 'items' => array(
771 + 'id' => array(
772 + 'description' => __( 'Item ID.', 'learnpress' ),
773 + 'type' => 'integer',
774 + 'context' => array( 'view', 'edit' ),
775 + ),
776 + 'type' => array(
777 + 'description' => __( 'Item Type.', 'learnpress' ),
778 + 'type' => 'string',
779 + 'context' => array( 'view', 'edit' ),
780 + ),
781 + 'title' => array(
782 + 'description' => __( 'Item title.', 'learnpress' ),
783 + 'type' => 'string',
784 + 'context' => array( 'view', 'edit' ),
785 + ),
786 + 'content' => array(
787 + 'description' => __( 'Item Content.', 'learnpress' ),
788 + 'type' => 'string',
789 + 'context' => array( 'view', 'edit' ),
790 + ),
791 + 'point' => array(
792 + 'description' => __( 'Point.', 'learnpress' ),
793 + 'type' => 'integer',
794 + 'context' => array( 'view', 'edit' ),
795 + ),
796 + 'hint' => array(
797 + 'description' => __( 'Question Hint.', 'learnpress' ),
798 + 'type' => 'string',
799 + 'context' => array( 'view', 'edit' ),
800 + ),
801 + 'options' => array(
802 + 'description' => __( 'Question Options.', 'learnpress' ),
803 + 'type' => 'array',
804 + 'context' => array( 'view', 'edit' ),
805 + 'items' => array(
806 + 'description' => __( 'Question items.', 'learnpress' ),
807 + 'type' => 'object',
808 + 'context' => array( 'view', 'edit' ),
809 + 'items' => array(
810 + 'title' => array(
811 + 'description' => __( 'Item title.', 'learnpress' ),
812 + 'type' => 'string',
813 + 'context' => array( 'view', 'edit' ),
814 + ),
815 + 'value' => array(
816 + 'description' => __( 'Item value.', 'learnpress' ),
817 + 'type' => 'string',
818 + 'context' => array( 'view', 'edit' ),
819 + ),
820 + 'uid' => array(
821 + 'description' => __( 'Item id.', 'learnpress' ),
822 + 'type' => 'integer',
823 + 'context' => array( 'view', 'edit' ),
824 + ),
825 + ),
826 + ),
827 + ),
828 + ),
829 + ),
830 + ),
831 + 'results' => array(
832 + 'description' => __( 'Retrieves the quiz result..', 'learnpress' ),
833 + 'type' => 'array',
834 + 'context' => array( 'view', 'edit' ),
835 + ),
836 + ),
837 + );
838 +
839 + return $this->add_additional_fields_schema( $schema );
840 + }
841 +}