'GET', 'callback' => array( static::class, 'get_logs' ), 'permission_callback' => array( static::class, 'check_permission' ), 'args' => array( 'filter' => array( 'required' => false, 'type' => 'string', 'default' => '1', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => function ( $param ) { return in_array( $param, array( '0', '1' ), true ); }, ), ), ) ); register_rest_route( self::$namespace, '/logs/clear', array( 'methods' => 'POST', 'callback' => array( static::class, 'clear_logs' ), 'permission_callback' => array( static::class, 'check_permission' ), ) ); register_rest_route( self::$namespace, '/logs/browser', array( 'methods' => 'POST', 'callback' => array( static::class, 'log_browser_console' ), 'permission_callback' => array( static::class, 'check_permission' ), 'args' => array( 'level' => array( 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'validate_callback' => function ( $param ) { return in_array( $param, array( 'log', 'info', 'warn', 'error', 'debug' ), true ); }, ), 'message' => array( 'required' => true, 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', ), 'data' => array( 'required' => false, 'type' => 'string', 'sanitize_callback' => 'sanitize_textarea_field', ), ), ) ); } public static function check_permission() { return current_user_can( 'manage_options' ); } public static function get_log_prefix() { return static::$log_prefix; } public static function get_log_path() { if ( defined( 'WP_DEBUG_LOG' ) && is_string( WP_DEBUG_LOG ) ) { return WP_DEBUG_LOG; } return WP_CONTENT_DIR . '/debug.log'; } public static function get_logs( $request ) { $log_path = static::get_log_path(); $prefix = static::get_log_prefix(); $apply_filter = '1' === $request->get_param( 'filter' ); if ( ! file_exists( $log_path ) ) { return new WP_REST_Response( array( 'success' => false, 'message' => __( 'Debug log file not found. Ensure WP_DEBUG_LOG is enabled.', self::$text_domain ), 'logs' => '', 'filtered' => $apply_filter, ), 200 ); } $lines = static::read_last_lines( $log_path, self::$max_lines ); if ( $apply_filter ) { $output = array(); foreach ( $lines as $line ) { if ( strpos( $line, $prefix ) !== false ) { $output[] = $line; } } } else { $output = array_filter( $lines, function ( $line ) { return '' !== trim( $line ); } ); } if ( empty( $output ) ) { $message = $apply_filter ? sprintf( /* translators: %1$s: prefix, %2$d: number of lines checked */ __( 'No %1$s entries found in the recent log data. Note: This viewer only shows the last %2$d lines of the log file.', self::$text_domain ), $prefix, self::$max_lines ) : __( 'Debug log is empty.', self::$text_domain ); return new WP_REST_Response( array( 'success' => true, 'message' => $message, 'logs' => '', 'count' => 0, 'filtered' => $apply_filter, ), 200 ); } return new WP_REST_Response( array( 'success' => true, 'message' => '', 'logs' => implode( "\n", $output ), 'count' => count( $output ), 'filtered' => $apply_filter, ), 200 ); } public static function clear_logs( $request ) { $log_path = static::get_log_path(); if ( ! file_exists( $log_path ) ) { return new WP_REST_Response( array( 'success' => true, 'cleared' => false, 'message' => __( 'Debug log file does not exist.', self::$text_domain ), ), 200 ); } if ( ! wp_is_writable( $log_path ) ) { return new WP_REST_Response( array( 'success' => false, 'cleared' => false, 'message' => __( 'Debug log file is not writable.', self::$text_domain ), ), 500 ); } $file_handle = fopen( $log_path, 'w' ); if ( false === $file_handle ) { return new WP_REST_Response( array( 'success' => false, 'cleared' => false, 'message' => __( 'Failed to clear debug log file.', self::$text_domain ), ), 500 ); } fclose( $file_handle ); if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { error_log( sprintf( '[%s] [ChimpMatic Lite] Debug log cleared by user: %s', gmdate( 'd-M-Y H:i:s' ) . ' UTC', wp_get_current_user()->user_login ) ); } return new WP_REST_Response( array( 'success' => true, 'cleared' => true, 'message' => __( 'Debug log cleared successfully.', self::$text_domain ), ), 200 ); } public static function log_browser_console( $request ) { $level = $request->get_param( 'level' ); $message = $request->get_param( 'message' ); $data = $request->get_param( 'data' ); $level_map = array( 'log' => 'INFO', 'info' => 'INFO', 'warn' => 'WARNING', 'error' => 'ERROR', 'debug' => 'DEBUG', ); $wp_level = $level_map[ $level ] ?? 'INFO'; $log_message = sprintf( '[%s] %s [Browser Console - %s] %s', gmdate( 'd-M-Y H:i:s' ) . ' UTC', static::$log_prefix, strtoupper( $level ), $message ); if ( ! empty( $data ) ) { $log_message .= ' | Data: ' . $data; } if ( defined( 'WP_DEBUG_LOG' ) && WP_DEBUG_LOG ) { error_log( $log_message ); } $logfile_enabled = (bool) get_option( CMATIC_LOG_OPTION, false ); $logger = new Cmatic_File_Logger( 'Browser-Console', $logfile_enabled ); $logger->log( $wp_level, 'Browser: ' . $message, $data ? json_decode( $data, true ) : null ); return new WP_REST_Response( array( 'success' => true, 'logged' => true, ), 200 ); } protected static function read_last_lines( $filepath, $lines = 500 ) { $handle = fopen( $filepath, 'r' ); if ( ! $handle ) { return array(); } $result = array(); $chunk = 4096; $file_size = filesize( $filepath ); if ( 0 === $file_size ) { fclose( $handle ); return array(); } $pos = $file_size; $buffer = ''; while ( $pos > 0 && count( $result ) < $lines ) { $read_size = min( $chunk, $pos ); $pos -= $read_size; fseek( $handle, $pos ); $buffer = fread( $handle, $read_size ) . $buffer; $buffer_lines = explode( "\n", $buffer ); $buffer = array_shift( $buffer_lines ); $result = array_merge( $buffer_lines, $result ); } if ( 0 === $pos && ! empty( $buffer ) ) { array_unshift( $result, $buffer ); } fclose( $handle ); return array_slice( $result, -$lines ); } public static function enqueue_assets( $hook ) { } protected static function get_inline_js() { $namespace = self::$namespace; return << __( 'Submission Logs', self::$text_domain ), 'clear_text' => __( 'Clear Logs', self::$text_domain ), 'placeholder' => __( 'Click "View Debug Logs" to fetch the log content.', self::$text_domain ), 'class' => '', ); $args = wp_parse_args( $args, $defaults ); ?>