Viewing File: /home/rtsgrob/ww5/wp-content/plugins/learnpress/inc/AI/Assistant/LanguageResolver.php

<?php

namespace LearnPress\AI\Assistant;

/**
 * LanguageResolver — detects learner language and generates instruction for the model.
 *
 * Completely stateless utility; can be reused across Agent and QuickQuizEngine.
 *
 * @package LearnPress\AI\Assistant
 * @since 4.3.5
 */
class LanguageResolver {

	/**
	 * Build strict language guidance so the model replies in the learner's language.
	 *
	 * @param string $user_message Current learner input.
	 * @param array  $history      Conversation history.
	 * @param int    $user_id      Current user ID.
	 *
	 * @return string
	 */
	public function build_instruction( string $user_message, array $history, int $user_id ): string {

		$sample      = $this->resolve_language_sample( $user_message, $history );
		$locale_hint = $this->resolve_locale_hint( $user_id );

		if ( $sample !== '' ) {
			return sprintf(
				/* translators: 1: learner message sample, 2: locale hint. */
				__( 'Language policy: reply strictly in the same language as the learner. Do not default to English. Learner sample: "%1$s". Locale fallback: %2$s.', 'learnpress' ),
				$sample,
				$locale_hint !== '' ? $locale_hint : 'n/a'
			);
		}

		return sprintf(
			/* translators: %s: locale hint. */
			__( 'Language policy: reply strictly in the learner language and do not default to English. If the latest learner message is language-neutral, use locale fallback: %s.', 'learnpress' ),
			$locale_hint !== '' ? $locale_hint : 'n/a'
		);
	}

	// ----------------------------------------------------------------
	// Private helpers
	// ----------------------------------------------------------------

	/**
	 * Pick the best text sample to infer learner language.
	 *
	 * Prefers the current message; falls back to previous user turns.
	 *
	 * @param string $user_message Current learner input.
	 * @param array  $history      Conversation history.
	 *
	 * @return string
	 */
	private function resolve_language_sample( string $user_message, array $history ): string {

		$current = $this->trim_text_for_prompt( $user_message );
		if ( $this->has_letters( $current ) && ! $this->is_language_neutral_command( $current ) ) {
			return $current;
		}

		for ( $index = count( $history ) - 1; $index >= 0; $index-- ) {
			$item = $history[ $index ] ?? array();
			if ( ( $item['role'] ?? '' ) !== 'user' || ! isset( $item['content'] ) ) {
				continue;
			}

			$content = $this->trim_text_for_prompt( (string) $item['content'] );
			if ( ! $this->has_letters( $content ) || $this->is_language_neutral_command( $content ) ) {
				continue;
			}

			return $content;
		}

		return '';
	}

	/**
	 * Resolve locale hint from WordPress user/site settings.
	 *
	 * @param int $user_id Current user ID.
	 *
	 * @return string
	 */
	private function resolve_locale_hint( int $user_id ): string {
		$locale = (string) get_user_locale( $user_id );
		if ( $locale !== '' ) {
			return $locale;
		}

		$locale = (string) determine_locale();
		if ( $locale !== '' ) {
			return $locale;
		}

		return (string) get_locale();
	}

	/**
	 * Determine if input is a language-neutral command (e.g. /quick-quiz, "1", "A", ...).
	 *
	 * @param string $text Input text.
	 *
	 * @return bool
	 */
	private function is_language_neutral_command( string $text ): bool {

		$normalized = trim( $text );
		if ( $normalized === '' ) {
			return true;
		}

		if ( str_starts_with( $normalized, '/' ) ) {
			return true;
		}

		return preg_match( '/^[\d\W_]+$/u', $normalized ) === 1;
	}

	/**
	 * Check whether input contains at least one letter character.
	 *
	 * @param string $text Input text.
	 *
	 * @return bool
	 */
	private function has_letters( string $text ): bool {
		return preg_match( '/\p{L}/u', $text ) === 1;
	}

	/**
	 * Trim and cap text length for safe system prompt injection.
	 *
	 * @param string $text Input text.
	 *
	 * @return string
	 */
	private function trim_text_for_prompt( string $text ): string {

		$clean = trim( preg_replace( '/\s+/', ' ', $text ) ?? '' );
		if ( $clean === '' ) {
			return '';
		}

		return mb_substr( $clean, 0, 220, 'UTF-8' );
	}
}
Back to Directory File Manager