← All changes
|
inc/jwt/rest-api/version1/class-lp-rest-lessons-v1-controller.php
+117
-24
4.1.7.2
→
4.4.10
View file →
| @@ -1,5 +1,12 @@ | ||
| 1 | 1 | <?php |
| 2 | + | |
| 3 | +use LearnPress\Models\CourseModel; | |
| 4 | +use LearnPress\Models\LessonPostModel; | |
| 5 | +use LearnPress\Models\UserItems\UserCourseModel; | |
| 6 | +use LearnPress\Models\UserItems\UserLessonModel; | |
| 7 | +use LearnPress\Models\UserModel; | |
| 8 | + | |
| 2 | 9 | class LP_Jwt_Lessons_V1_Controller extends LP_REST_Jwt_Posts_Controller { |
| 3 | 10 | protected $namespace = 'learnpress/v1'; |
| 4 | 11 | |
| 5 | 12 | protected $rest_base = 'lessons'; |
| @@ -28,9 +35,9 @@ | ||
| 28 | 35 | '/' . $this->rest_base . '/(?P<id>[\d]+)', |
| 29 | 36 | array( |
| 30 | 37 | 'args' => array( |
| 31 | 38 | 'id' => array( |
| 32 | - 'description' => esc_html__( 'Unique identifier for the resource.', 'learnpress' ), | |
| 39 | + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ), | |
| 33 | 40 | 'type' => 'integer', |
| 34 | 41 | ), |
| 35 | 42 | ), |
| 36 | 43 | array( |
| @@ -54,9 +61,9 @@ | ||
| 54 | 61 | '/' . $this->rest_base . '/finish', |
| 55 | 62 | array( |
| 56 | 63 | 'args' => array( |
| 57 | 64 | 'id' => array( |
| 58 | - 'description' => esc_html__( 'Unique identifier for the resource.', 'learnpress' ), | |
| 65 | + 'description' => esc_html__( 'A unique identifier for the resource.', 'learnpress' ), | |
| 59 | 66 | 'type' => 'integer', |
| 60 | 67 | ), |
| 61 | 68 | ), |
| 62 | 69 | array( |
| @@ -75,8 +82,17 @@ | ||
| 75 | 82 | ); |
| 76 | 83 | } |
| 77 | 84 | |
| 78 | 85 | /** |
| 86 | + * Allow guests/unauthenticated users to query the list endpoint (filtered by slug). | |
| 87 | + * Per-item visibility is still enforced by check_read_permission, which only | |
| 88 | + * allows preview items for guests. | |
| 89 | + */ | |
| 90 | + public function get_items_permissions_check( $request ) { | |
| 91 | + return true; | |
| 92 | + } | |
| 93 | + | |
| 94 | + /** | |
| 79 | 95 | * Checks if a course can be read. |
| 80 | 96 | * |
| 81 | 97 | * Correctly handles courses with the inherit status. |
| 82 | 98 | * |
| @@ -103,8 +119,20 @@ | ||
| 103 | 119 | if ( ! $post_status_obj || ! $post_status_obj->public ) { |
| 104 | 120 | return false; |
| 105 | 121 | } |
| 106 | 122 | |
| 123 | + // Allow guests and unenrolled users to read items marked as preview. | |
| 124 | + $course_id = $this->get_course_by_item_id( $post_id ); | |
| 125 | + if ( $course_id ) { | |
| 126 | + $course = learn_press_get_course( $course_id ); | |
| 127 | + if ( $course ) { | |
| 128 | + $item = $course->get_item( $post_id ); | |
| 129 | + if ( $item && $item->is_preview() ) { | |
| 130 | + return true; | |
| 131 | + } | |
| 132 | + } | |
| 133 | + } | |
| 134 | + | |
| 107 | 135 | $user_id = get_current_user_id(); |
| 108 | 136 | |
| 109 | 137 | if ( ! $user_id ) { |
| 110 | 138 | return false; |
| @@ -111,11 +139,8 @@ | ||
| 111 | 139 | } |
| 112 | 140 | |
| 113 | 141 | $user = learn_press_get_user( $user_id ); |
| 114 | 142 | |
| 115 | - // Get course ID by lesson ID assigned. | |
| 116 | - $course_id = $this->get_course_by_item_id( $post_id ); | |
| 117 | - | |
| 118 | 143 | if ( empty( $course_id ) ) { |
| 119 | 144 | return false; |
| 120 | 145 | } |
| 121 | 146 | |
| @@ -190,41 +215,94 @@ | ||
| 190 | 215 | return false; |
| 191 | 216 | } |
| 192 | 217 | |
| 193 | 218 | public function finish_lesson( $request ) { |
| 194 | - $response = new LP_REST_Response(); | |
| 195 | - $response->data = new stdClass(); | |
| 219 | + $response = new LP_REST_Response(); | |
| 196 | 220 | |
| 197 | 221 | try { |
| 198 | 222 | $id = isset( $request['id'] ) ? absint( $request['id'] ) : ''; |
| 199 | - | |
| 200 | 223 | if ( empty( $id ) ) { |
| 201 | 224 | throw new Exception( esc_html__( 'Error: No lesson available!.', 'learnpress' ) ); |
| 202 | 225 | } |
| 203 | 226 | |
| 227 | + $lessonModel = LessonPostModel::find( $id, true ); | |
| 228 | + if ( ! $lessonModel ) { | |
| 229 | + throw new Exception( esc_html__( 'Error: Lesson is invalid!', 'learnpress' ) ); | |
| 230 | + } | |
| 231 | + | |
| 204 | 232 | $course_id = $this->get_course_by_item_id( $id ); |
| 205 | - | |
| 206 | 233 | if ( empty( $course_id ) ) { |
| 207 | - throw new Exception( esc_html__( 'Error: This lesson is not assign in Course.', 'learnpress' ) ); | |
| 234 | + throw new Exception( esc_html__( 'Error: This lesson is not assigned in the Course.', 'learnpress' ) ); | |
| 208 | 235 | } |
| 209 | 236 | |
| 210 | - $course = learn_press_get_course( $course_id ); | |
| 211 | - $user = learn_press_get_current_user(); | |
| 237 | + $courseModel = CourseModel::find( $course_id, true ); | |
| 238 | + $userModel = UserModel::find( get_current_user_id(), true ); | |
| 212 | 239 | |
| 213 | - if ( empty( $course ) || empty( $user ) ) { | |
| 240 | + if ( empty( $courseModel ) || empty( $userModel ) ) { | |
| 214 | 241 | throw new Exception( esc_html__( 'Error: No Course or User available.', 'learnpress' ) ); |
| 215 | 242 | } |
| 216 | 243 | |
| 217 | - $result = $user->complete_lesson( $id, $course_id ); | |
| 244 | + $userLessonModel = UserLessonModel::find_user_item( | |
| 245 | + get_current_user_id(), | |
| 246 | + $id, | |
| 247 | + $lessonModel->post_type, | |
| 248 | + $course_id, | |
| 249 | + LP_COURSE_CPT, | |
| 250 | + true | |
| 251 | + ); | |
| 218 | 252 | |
| 219 | - if ( is_wp_error( $result ) ) { | |
| 220 | - throw new Exception( $result->get_error_message() ?? esc_html__( 'Error: Cannot complete Lesson', 'learnpress' ) ); | |
| 253 | + // Auto-create user lesson item if user is enrolled but never opened the lesson. | |
| 254 | + if ( ! $userLessonModel instanceof UserLessonModel ) { | |
| 255 | + $userCourseModel = UserCourseModel::find( get_current_user_id(), $course_id, true ); | |
| 256 | + if ( ! $userCourseModel instanceof UserCourseModel || ! $userCourseModel->has_enrolled_or_finished() ) { | |
| 257 | + throw new Exception( __( 'You must enroll the course first.', 'learnpress' ) ); | |
| 258 | + } | |
| 259 | + | |
| 260 | + $userLessonModel = new UserLessonModel(); | |
| 261 | + $userLessonModel->user_id = get_current_user_id(); | |
| 262 | + $userLessonModel->item_id = $id; | |
| 263 | + $userLessonModel->item_type = $lessonModel->post_type; | |
| 264 | + $userLessonModel->ref_id = $course_id; | |
| 265 | + $userLessonModel->ref_type = LP_COURSE_CPT; | |
| 266 | + $userLessonModel->parent_id = $userCourseModel->get_user_item_id(); | |
| 267 | + $userLessonModel->status = LP_ITEM_STARTED; | |
| 268 | + $userLessonModel->save(); | |
| 269 | + } elseif ( empty( $userLessonModel->parent_id ) ) { | |
| 270 | + // Repair legacy rows that were created without parent_id — without it, the lesson | |
| 271 | + // won't be counted by UserCourseModel::count_items_completed(). | |
| 272 | + $userCourseModel = UserCourseModel::find( get_current_user_id(), $course_id, true ); | |
| 273 | + if ( $userCourseModel instanceof UserCourseModel ) { | |
| 274 | + $userLessonModel->parent_id = $userCourseModel->get_user_item_id(); | |
| 275 | + $userLessonModel->save(); | |
| 276 | + } | |
| 221 | 277 | } |
| 222 | 278 | |
| 279 | + $userLessonModel->set_complete(); | |
| 280 | + | |
| 281 | + // Recalculate course progress and persist to learnpress_user_item_results. | |
| 282 | + $userCourseModel = $userLessonModel->get_user_course_model(); | |
| 283 | + if ( $userCourseModel instanceof UserCourseModel ) { | |
| 284 | + $course_results = $userCourseModel->calculate_course_results( true ); | |
| 285 | + LP_User_Items_Result_DB::instance()->update( | |
| 286 | + $userCourseModel->get_user_item_id(), | |
| 287 | + wp_json_encode( $course_results ) | |
| 288 | + ); | |
| 289 | + | |
| 290 | + // Auto-finish the course when evaluation criteria are met. | |
| 291 | + $can_finish = $userCourseModel->can_finish(); | |
| 292 | + if ( $can_finish === true && ! is_wp_error( $can_finish ) ) { | |
| 293 | + try { | |
| 294 | + $userCourseModel->set_finish(); | |
| 295 | + } catch ( Throwable $finishError ) { | |
| 296 | + // Non-fatal — user can still click Finish manually. | |
| 297 | + error_log( __METHOD__ . ' auto-finish: ' . $finishError->getMessage() ); | |
| 298 | + } | |
| 299 | + } | |
| 300 | + } | |
| 301 | + | |
| 223 | 302 | $response->status = 'success'; |
| 224 | - $response->message = esc_html__( 'Congrats! You complete lesson is successfully', 'learnpress' ); | |
| 225 | - } catch ( \Throwable $th ) { | |
| 226 | - $response->status = 'error'; | |
| 303 | + $response->message = esc_html__( 'Congrats! You have completed the lesson successfully', 'learnpress' ); | |
| 304 | + } catch ( Throwable $th ) { | |
| 227 | 305 | $response->message = $th->getMessage(); |
| 228 | 306 | } |
| 229 | 307 | |
| 230 | 308 | return rest_ensure_response( $response ); |
| @@ -248,13 +326,28 @@ | ||
| 248 | 326 | $data = array(); |
| 249 | 327 | |
| 250 | 328 | $assigned = $this->get_assigned( $id ); |
| 251 | 329 | |
| 330 | + $course_id = 0; | |
| 252 | 331 | if ( ! empty( $assigned ) && method_exists( $object, 'set_course' ) ) { |
| 253 | 332 | $course_id = $assigned['course']['id']; |
| 254 | 333 | $object->set_course( $course_id ); |
| 255 | 334 | } |
| 256 | 335 | |
| 336 | + $userLessonModel = UserLessonModel::find_user_item( | |
| 337 | + get_current_user_id(), | |
| 338 | + $id, | |
| 339 | + LP_LESSON_CPT, | |
| 340 | + $course_id, | |
| 341 | + LP_COURSE_CPT, | |
| 342 | + true | |
| 343 | + ); | |
| 344 | + $end_time = ''; | |
| 345 | + | |
| 346 | + if ( $userLessonModel instanceof UserLessonModel ) { | |
| 347 | + $end_time = $userLessonModel->get_end_time(); | |
| 348 | + } | |
| 349 | + | |
| 257 | 350 | foreach ( $fields as $field ) { |
| 258 | 351 | switch ( $field ) { |
| 259 | 352 | case 'id': |
| 260 | 353 | $data['id'] = $id; |
| @@ -306,8 +399,9 @@ | ||
| 306 | 399 | break; |
| 307 | 400 | } |
| 308 | 401 | } |
| 309 | 402 | |
| 403 | + $data['end_time'] = $end_time; | |
| 310 | 404 | $data['meta_data'] = $this->get_course_meta( $id ); |
| 311 | 405 | |
| 312 | 406 | return $data; |
| 313 | 407 | } |
| @@ -329,9 +423,9 @@ | ||
| 329 | 423 | public function check_can_finish_course( $id ) { |
| 330 | 424 | $user = learn_press_get_current_user(); |
| 331 | 425 | |
| 332 | 426 | if ( ! $user || ! $id ) { |
| 333 | - return falase; | |
| 427 | + return false; | |
| 334 | 428 | } |
| 335 | 429 | |
| 336 | 430 | $course_id = $this->get_course_by_item_id( $id ); |
| 337 | 431 | |
| @@ -426,9 +520,9 @@ | ||
| 426 | 520 | 'title' => $this->post_type, |
| 427 | 521 | 'type' => 'object', |
| 428 | 522 | 'properties' => array( |
| 429 | 523 | 'id' => array( |
| 430 | - 'description' => __( 'Unique identifier for the resource.', 'learnpress' ), | |
| 524 | + 'description' => __( 'A unique identifier for the resource.', 'learnpress' ), | |
| 431 | 525 | 'type' => 'integer', |
| 432 | 526 | 'context' => array( 'view', 'edit' ), |
| 433 | 527 | 'readonly' => true, |
| 434 | 528 | ), |
| @@ -480,9 +574,9 @@ | ||
| 480 | 574 | 'enum' => array_merge( array_keys( get_post_statuses() ), array( 'future' ) ), |
| 481 | 575 | 'context' => array( 'view', 'edit' ), |
| 482 | 576 | ), |
| 483 | 577 | 'content' => array( |
| 484 | - 'description' => __( 'Content course.', 'learnpress' ), | |
| 578 | + 'description' => __( 'Course content.', 'learnpress' ), | |
| 485 | 579 | 'type' => 'string', |
| 486 | 580 | 'context' => array( 'view', 'edit' ), |
| 487 | 581 | ), |
| 488 | 582 | 'excerpt' => array( |
| @@ -490,9 +584,9 @@ | ||
| 490 | 584 | 'type' => 'string', |
| 491 | 585 | 'context' => array( 'view', 'edit' ), |
| 492 | 586 | ), |
| 493 | 587 | 'can_finish_course' => array( |
| 494 | - 'description' => __( 'Can finish course', 'learnpress' ), | |
| 588 | + 'description' => __( 'Can finish the course', 'learnpress' ), | |
| 495 | 589 | 'type' => 'boolean', |
| 496 | 590 | 'context' => array( 'view' ), |
| 497 | 591 | 'readonly' => true, |
| 498 | 592 | ), |
| @@ -601,6 +695,5 @@ | ||
| 601 | 695 | } |
| 602 | 696 | |
| 603 | 697 | return $response; |
| 604 | 698 | } |
| 605 | - | |
| 606 | 699 | } |