validate_debug_log_request() ) { return; } $lines = $this->get_requested_lines(); $log_file = $this->get_log_file_path(); if ( ! $this->log_file_exists( $log_file ) ) { $this->send_file_not_found_response( $lines ); return; } $log_content = $this->get_log_content( $log_file, $lines ); $this->send_log_content_response( $log_content, $lines, $log_file ); } /** * Validates the debug log request permissions and security * * @return bool */ private function validate_debug_log_request() { if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( __( 'Permission denied', 'accessibility-audio-tts-text-to-speech-for-articles' ) ); return false; } $nonce = isset( $_POST['nonce'] ) ? sanitize_text_field( wp_unslash( $_POST['nonce'] ) ) : ''; if ( ! wp_verify_nonce( $nonce, 'accessibility_audio_tts_debug_nonce' ) ) { wp_send_json_error( __( 'Invalid nonce', 'accessibility-audio-tts-text-to-speech-for-articles' ) ); return false; } return true; } /** * Gets the number of lines requested with validation * * @return int */ private function get_requested_lines() { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Called from methods that verify nonce $lines = isset( $_POST['lines'] ) ? absint( wp_unslash( $_POST['lines'] ) ) : 50; return max( 10, min( 200, $lines ) ); // Minimum 10, maximum 200 lines. } /** * Gets the debug log file path * * @return string */ private function get_log_file_path() { return \Accessibility_Audio_TTS\Core\Privacy_Lifecycle::latest_log_file(); } /** * Checks if log file exists * * @param string $log_file Log file path. * @return bool */ private function log_file_exists( $log_file ) { return '' !== $log_file && ! is_link( $log_file ) && is_file( $log_file ) && is_readable( $log_file ); } /** * Sends response when log file doesn't exist * * @param int $lines Number of lines requested. */ private function send_file_not_found_response( $lines ) { wp_send_json_success( array( 'log' => __( 'Debug log does not exist', 'accessibility-audio-tts-text-to-speech-for-articles' ), 'lines_requested' => $lines, 'file_exists' => false, ) ); } /** * Gets log content from file * * @param string $log_file Log file path. * @param int $lines Number of lines to retrieve. * @return string */ private function get_log_content( $log_file, $lines ) { return $this->tail_file( $log_file, $lines ); } /** * Sends successful log content response * * @param string $log_content Log content. * @param int $lines Number of lines requested. * @param string $log_file Log file path. */ private function send_log_content_response( $log_content, $lines, $log_file ) { wp_send_json_success( array( 'log' => $log_content, 'lines_requested' => $lines, 'lines_returned' => substr_count( $log_content, "\n" ), 'file_size' => filesize( $log_file ), 'file_exists' => true, 'timestamp' => current_time( 'mysql' ), ) ); } /** * Get last N lines from file (like tail in bash) * * @param string $file File path. * @param int $lines Number of lines. * @return string */ private function tail_file( $file, $lines = 50 ) { return \Accessibility_Audio_TTS\Core\Privacy_Lifecycle::read_log_tail( $file, $lines ); } /** * Reads content from small files * * @param string $file File path. * @param int $lines Number of lines. * @return string */ private function read_small_file( $file, $lines ) { return $this->tail_file( $file, $lines ); } /** * Reads content from large files by reading backwards * * @param string $file File path. * @param int $lines Number of lines. * @return string */ private function read_large_file( $file, $lines ) { // Fallback to full read and slice lines (avoids direct fopen/fclose). return $this->read_small_file( $file, $lines ); } }