← All changes
|
inc/TemplateHooks/Course/CourseAIAssistantTemplate.php
+20
-0
4.4.1
→
4.4.8
View file →
| @@ -126,11 +126,27 @@ | ||
| 126 | 126 | |
| 127 | 127 | $context = $this->detect_context(); |
| 128 | 128 | $item = LP_Global::course_item(); |
| 129 | 129 | $item_id = $item ? absint( $item->get_id() ) : 0; |
| 130 | + $item_type = $item ? (string) $item->get_item_type() : ''; | |
| 130 | 131 | $course_id = $item ? absint( $item->get_course_id() ) : 0; |
| 131 | 132 | $user_id = get_current_user_id(); |
| 132 | 133 | |
| 134 | + /** | |
| 135 | + * Defense in depth: run the same resolver the AJAX controller uses, so the widget | |
| 136 | + * is never offered for an item the user cannot view. This is not the security | |
| 137 | + * boundary — AIAssistantController::handle_chat() is, because the AJAX action is | |
| 138 | + * reachable without this markup ever rendering. | |
| 139 | + * | |
| 140 | + * Catches Throwable because this runs on wp_enqueue_scripts, outside the | |
| 141 | + * render_panel() try/catch. Any failure denies rather than fatals the page. | |
| 142 | + */ | |
| 143 | + try { | |
| 144 | + AIAssistantController::resolve_item_access( $user_id, $course_id, $item_type, $item_id ); | |
| 145 | + } catch ( Throwable $e ) { | |
| 146 | + return $this->render_state = false; | |
| 147 | + } | |
| 148 | + | |
| 133 | 149 | $enabled_actions = AIAssistantController::get_enabled_actions(); |
| 134 | 150 | $free_chat_enabled = LP_Settings::get_option( 'ai_assistant_free_chat', 'no' ) === 'yes'; |
| 135 | 151 | |
| 136 | 152 | if ( $context === 'quiz' ) { |
| @@ -162,8 +178,9 @@ | ||
| 162 | 178 | |
| 163 | 179 | return $this->render_state = array( |
| 164 | 180 | 'context' => $context, |
| 165 | 181 | 'item_id' => $item_id, |
| 182 | + 'item_type' => $item_type, | |
| 166 | 183 | 'course_id' => $course_id, |
| 167 | 184 | 'enabled_actions' => $enabled_actions, |
| 168 | 185 | 'free_chat_enabled' => $free_chat_enabled, |
| 169 | 186 | 'quiz_result' => $quiz_result, |
| @@ -181,8 +198,11 @@ | ||
| 181 | 198 | 'ajaxUrl' => LP_Settings::url_handle_lp_ajax(), |
| 182 | 199 | 'nonce' => wp_create_nonce( 'wp_rest' ), |
| 183 | 200 | 'lessonId' => $render_state['item_id'], |
| 184 | 201 | 'itemId' => $render_state['item_id'], |
| 202 | + // Server-resolved curriculum type. The client echoes it back as item_type | |
| 203 | + // and the server re-validates it; it is transport, not proof. | |
| 204 | + 'itemType' => $render_state['item_type'], | |
| 185 | 205 | 'courseId' => $render_state['course_id'], |
| 186 | 206 | 'context' => $render_state['context'], |
| 187 | 207 | 'quizCompleted' => $render_state['context'] === 'quiz', |
| 188 | 208 | 'quizResult' => $render_state['quiz_result'], |