Viewing File: /home/rtsgrob/ww5/wp-content/plugins/learnpress/inc/AI/Assistant/AIAssistantController.php
<?php
namespace LearnPress\AI\Assistant;
use Exception;
use LP_Settings;
use LearnPress\Services\OpenAiService;
/**
* AI Assistant Controller — validates requests, sanitizes input, calls Agent.
*
* Entry point for the AJAX layer. Guarantees the response structure
* required by the frontend: { type, message, quiz }.
*
* @package LearnPress\AI\Assistant
* @since 4.3.5
*/
class AIAssistantController {
private const ACTION_SETTINGS = array(
'summarize' => 'ai_assistant_summarize_enabled',
'explain' => 'ai_assistant_explain_enabled',
'quick_quiz' => 'ai_assistant_quick_quiz_enabled',
'smart_review' => 'ai_assistant_smart_review_enabled',
);
/**
* Check if the AI Assistant feature is fully enabled.
*
* All three gates must pass:
* - enable_open_ai = yes
* - secret key exists
* - ai_assistant_enabled = yes
*
* @return bool
*/
public static function is_enabled(): bool {
$service = OpenAiService::instance();
if ( ! $service->is_enable() ) {
return false;
}
if ( empty( LP_Settings::get_option( 'open_ai_secret_key', '' ) ) ) {
return false;
}
return LP_Settings::get_option( 'ai_assistant_enabled', 'no' ) === 'yes';
}
/**
* Resolve per-action enabled flags from admin settings.
*
* @return array<string, bool>
*/
public static function get_enabled_actions(): array {
$enabled_actions = array();
foreach ( self::ACTION_SETTINGS as $action => $setting_key ) {
$enabled_actions[ $action ] = LP_Settings::get_option( $setting_key, 'yes' ) === 'yes';
}
return $enabled_actions;
}
/**
* Check whether a specific assistant action is enabled.
*
* @param string $action Action slug.
*
* @return bool
*/
public static function is_action_enabled( string $action ): bool {
$enabled_actions = self::get_enabled_actions();
return $enabled_actions[ $action ] ?? true;
}
/**
* Handle an assistant chat request.
*
* @param array $data Raw decoded data from the AJAX request.
*
* @return array{type: string, message: string, quiz: array|null}
* @throws Exception On validation failure or API error.
*/
public function handle_chat( array $data ): array {
$message = trim( $data['message'] ?? '' );
$item_id = absint( $data['item_id'] ?? 0 );
$course_id = absint( $data['course_id'] ?? 0 );
$history = $data['history'] ?? array();
$quiz_data = $data['active_quiz_questions'] ?? array();
$action_hint = $this->sanitize_action_hint( $data['action_hint'] ?? '' );
if ( $message === '' ) {
throw new Exception( __( 'Message is required.', 'learnpress' ) );
}
if ( empty( $item_id ) ) {
throw new Exception( __( 'Item ID is required.', 'learnpress' ) );
}
if ( $course_id === 0 ) {
throw new Exception( __( 'Course ID is required.', 'learnpress' ) );
}
$user_id = get_current_user_id();
if ( $user_id === 0 ) {
throw new Exception( __( 'User must be logged in.', 'learnpress' ) );
}
// Sanitize history — only allow safe role/content pairs.
$sanitized_history = array();
if ( is_array( $history ) ) {
foreach ( $history as $msg ) {
$role = $msg['role'] ?? '';
$content = $msg['content'] ?? '';
if ( in_array( $role, array( 'user', 'assistant' ), true ) && is_string( $content ) ) {
$sanitized_history[] = array(
'role' => $role,
'content' => sanitize_textarea_field( $content ),
);
}
}
}
$agent = new Agent();
$sanitized_quiz_state = $this->sanitize_active_quiz_state( $quiz_data );
return $agent->run(
sanitize_textarea_field( $message ),
$item_id,
$course_id,
$user_id,
$sanitized_history,
$sanitized_quiz_state,
$action_hint
);
}
/**
* Sanitize optional quick-action hint from frontend.
*
* @param mixed $action_hint Raw action hint.
*
* @return string|null
*/
private function sanitize_action_hint( $action_hint ): ?string {
if ( ! is_scalar( $action_hint ) ) {
return null;
}
$normalized = strtolower( trim( (string) $action_hint ) );
if ( '' === $normalized ) {
return null;
}
$normalized = str_replace( '-', '_', $normalized );
$aliases = array(
'quick_quiz' => 'quick_quiz',
'summarize' => 'summarize',
'explain' => 'explain',
'smart_review' => 'smart_review',
);
return $aliases[ $normalized ] ?? null;
}
/**
* Sanitize active quick-quiz state from frontend.
*
* @param mixed $quiz_data
*
* @return array
*/
private function sanitize_active_quiz_state( $quiz_data ): array {
if ( ! is_array( $quiz_data ) ) {
return array();
}
$questions = array();
if ( ! empty( $quiz_data['questions'] ) && is_array( $quiz_data['questions'] ) ) {
foreach ( $quiz_data['questions'] as $question ) {
if ( ! is_array( $question ) ) {
continue;
}
$options = array();
if ( ! empty( $question['options'] ) && is_array( $question['options'] ) ) {
foreach ( $question['options'] as $option ) {
$options[] = sanitize_text_field( (string) $option );
}
}
$questions[] = array(
'question' => sanitize_text_field( (string) ( $question['question'] ?? '' ) ),
'options' => $options,
'correct_index' => absint( $question['correct_index'] ?? 0 ),
'explanation' => sanitize_textarea_field( (string) ( $question['explanation'] ?? '' ) ),
);
}
}
return array(
'is_active' => ! empty( $quiz_data['is_active'] ),
'completed' => ! empty( $quiz_data['completed'] ),
'current_index' => absint( $quiz_data['current_index'] ?? 0 ),
'score' => absint( $quiz_data['score'] ?? 0 ),
'total' => absint( $quiz_data['total'] ?? count( $questions ) ),
'questions' => $questions,
);
}
}
Back to Directory
File Manager