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 +108 -18 4.1.7 → 4.4.10 View file →
@@ -28,9 +28,9 @@
28 28 '/' . $this->rest_base . '/(?P<id>[\d]+)',
29 29 array(
30 30 'args' => array(
31 31 'id' => array(
32 - 'description' => esc_html__( 'Unique identifier for the resource.', 'learnpress' ),
32 + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ),
33 33 'type' => 'integer',
34 34 ),
35 35 ),
36 36 array(
@@ -98,9 +98,9 @@
98 98 'description' => esc_html__( 'Quiz ID.', 'learnpress' ),
99 99 'type' => 'integer',
100 100 ),
101 101 'answered' => array(
102 - 'description' => esc_html__( 'Answer all question.', 'learnpress' ),
102 + 'description' => esc_html__( 'Answer all questions.', 'learnpress' ),
103 103 'type' => 'object',
104 104 ),
105 105 ),
106 106 array(
@@ -112,8 +112,17 @@
112 112 );
113 113 }
114 114
115 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 + /**
116 125 * Checks if a course can be read.
117 126 *
118 127 * Correctly handles courses with the inherit status.
119 128 *
@@ -140,8 +149,20 @@
140 149 if ( ! $post_status_obj || ! $post_status_obj->public ) {
141 150 return false;
142 151 }
143 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 +
144 165 $user_id = get_current_user_id();
145 166
146 167 if ( ! $user_id ) {
147 168 return false;
@@ -148,11 +169,8 @@
148 169 }
149 170
150 171 $user = learn_press_get_user( $user_id );
151 172
152 - // Get course ID by lesson ID assigned.
153 - $course_id = $this->get_course_by_item_id( $post_id );
154 -
155 173 if ( empty( $course_id ) ) {
156 174 return false;
157 175 }
158 176
@@ -227,9 +245,9 @@
227 245 $course_id = $this->get_course_by_item_id( $quiz_id );
228 246
229 247 if ( empty( $quiz_id ) || empty( $course_id ) ) {
230 248 $response->status = 'error';
231 - $response->message = esc_html__( 'No Quiz ID or Quiz is not assigned in Course.', 'learnpress' );
249 + $response->message = esc_html__( 'There is no Quiz ID or Quiz assigned in the course.', 'learnpress' );
232 250
233 251 return rest_ensure_response( $response );
234 252 }
235 253
@@ -253,9 +271,9 @@
253 271 $course_id = $this->get_course_by_item_id( $quiz_id );
254 272
255 273 if ( empty( $quiz_id ) || empty( $course_id ) ) {
256 274 $response->status = 'error';
257 - $response->message = esc_html__( 'No Quiz ID or Quiz is not assigned in Course.', 'learnpress' );
275 + $response->message = esc_html__( 'There is no Quiz ID or Quiz assigned in the course.', 'learnpress' );
258 276
259 277 return rest_ensure_response( $response );
260 278 }
261 279
@@ -281,9 +299,9 @@
281 299 $course_id = $this->get_course_by_item_id( $quiz_id );
282 300
283 301 if ( empty( $quiz_id ) || empty( $course_id ) ) {
284 302 $response->status = 'error';
285 - $response->message = esc_html__( 'No Quiz ID or Quiz is not assigned in Course.', 'learnpress' );
303 + $response->message = esc_html__( 'There is no Quiz ID or Quiz assigned in the course.', 'learnpress' );
286 304
287 305 return rest_ensure_response( $response );
288 306 }
289 307
@@ -288,9 +306,9 @@
288 306 }
289 307
290 308 if ( ! isset( $request['answered'] ) ) {
291 309 $response->status = 'error';
292 - $response->message = esc_html__( 'No Answed param.', 'learnpress' );
310 + $response->message = esc_html__( 'No Answer param.', 'learnpress' );
293 311
294 312 return rest_ensure_response( $response );
295 313 }
296 314
@@ -303,9 +321,9 @@
303 321 $request->set_param( 'answered', $answered );
304 322 $request->set_param( 'course_id', $course_id );
305 323 $request->set_param( 'item_id', $quiz_id );
306 324
307 - return $controller->submit_quiz_new( $request );
325 + return $controller->submit_quiz( $request );
308 326 }
309 327
310 328 public function prepare_object_for_response( $object, $request ) {
311 329 $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
@@ -391,9 +409,9 @@
391 409 public function check_can_finish_course( $id ) {
392 410 $user = learn_press_get_current_user();
393 411
394 412 if ( ! $user || ! $id ) {
395 - return falase;
413 + return false;
396 414 }
397 415
398 416 $course_id = $this->get_course_by_item_id( $id );
399 417
@@ -453,9 +471,9 @@
453 471 $output = array(
454 472 'status' => $status,
455 473 'attempts' => $user_quiz->get_attempts(),
456 474 'checked_questions' => $checked_questions,
457 - 'start_time' => lp_jwt_prepare_date_response( $user_quiz->get_start_time()->toSql( false ) ),
475 + 'start_time' => lp_jwt_prepare_date_response( $user_quiz->get_start_time()->toSql() ),
458 476 'retaken' => absint( $user_quiz->get_retaken_count() ),
459 477 );
460 478
461 479 $output['total_time'] = $user_quiz->get_timestamp_remaining();
@@ -471,14 +489,18 @@
471 489 }
472 490
473 491 $duration = $quiz->get_duration();
474 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 +
475 497 $array = array(
476 498 'passing_grade' => $quiz->get_passing_grade(),
477 499 'negative_marking' => $quiz->get_negative_marking(),
478 500 'instant_check' => $quiz->get_instant_check(),
479 - 'retake_count' => absint( $quiz->get_retake_count() ),
480 - 'questions_per_page' => $quiz->get_pagination(),
501 + 'retake_count' => (float) $quiz->get_retake_count(),
502 + 'questions_per_page' => $pagination_raw > 0 ? $pagination_raw : 0,
481 503 'page_numbers' => get_post_meta( $quiz->get_id(), '_lp_pagination_numbers', true ) === 'yes',
482 504 'review_questions' => $quiz->get_review_questions(),
483 505 'support_options' => learn_press_get_question_support_answer_options(),
484 506 'duration' => $duration ? $duration->get() : false,
@@ -495,8 +517,76 @@
495 517 'show_correct_review' => $show_correct_review,
496 518 )
497 519 );
498 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 +
499 589 $output['questions'] = $questions;
500 590 }
501 591
502 592 return array_merge( $array, $output );
@@ -562,9 +652,9 @@
562 652 'title' => $this->post_type,
563 653 'type' => 'object',
564 654 'properties' => array(
565 655 'id' => array(
566 - 'description' => __( 'Unique identifier for the resource.', 'learnpress' ),
656 + 'description' => __( 'A unique identifier for the resource.', 'learnpress' ),
567 657 'type' => 'integer',
568 658 'context' => array( 'view', 'edit' ),
569 659 'readonly' => true,
570 660 ),
@@ -616,9 +706,9 @@
616 706 'enum' => array_merge( array_keys( get_post_statuses() ), array( 'future' ) ),
617 707 'context' => array( 'view', 'edit' ),
618 708 ),
619 709 'content' => array(
620 - 'description' => __( 'Content course.', 'learnpress' ),
710 + 'description' => __( 'Course content.', 'learnpress' ),
621 711 'type' => 'string',
622 712 'context' => array( 'view', 'edit' ),
623 713 ),
624 714 'excerpt' => array(
@@ -626,9 +716,9 @@
626 716 'type' => 'string',
627 717 'context' => array( 'view', 'edit' ),
628 718 ),
629 719 'can_finish_course' => array(
630 - 'description' => __( 'Can finish course', 'learnpress' ),
720 + 'description' => __( 'Can finish the course', 'learnpress' ),
631 721 'type' => 'boolean',
632 722 'context' => array( 'view' ),
633 723 'readonly' => true,
634 724 ),
@@ -669,9 +759,9 @@
669 759 ),
670 760 ),
671 761 ),
672 762 'questions' => array(
673 - 'description' => __( 'List all Question in Quiz.', 'learnpress' ),
763 + 'description' => __( 'List all of the Quiz Questions.', 'learnpress' ),
674 764 'type' => 'array',
675 765 'context' => array( 'view', 'edit' ),
676 766 'items' => array(
677 767 'description' => __( 'Question items.', 'learnpress' ),