log_dir = trailingslashit( $upload_dir['basedir'] ) . 'aitm-logs'; $this->log_file = trailingslashit( $this->log_dir ) . 'generation.log'; $this->ensure_log_directory(); } /** * Ensure log directory exists and is protected * * @return bool True on success, false on failure. */ private function ensure_log_directory() { if ( ! file_exists( $this->log_dir ) ) { if ( ! wp_mkdir_p( $this->log_dir ) ) { return false; } } // Create .htaccess to deny direct access $htaccess_file = trailingslashit( $this->log_dir ) . '.htaccess'; if ( ! file_exists( $htaccess_file ) ) { $htaccess_content = "# Deny all requests\n\n\tOrder allow,deny\n\tDeny from all\n"; file_put_contents( $htaccess_file, $htaccess_content ); } // Create index.php to prevent directory listing $index_file = trailingslashit( $this->log_dir ) . 'index.php'; if ( ! file_exists( $index_file ) ) { file_put_contents( $index_file, ' $value ) { if ( is_array( $value ) || is_object( $value ) ) { $value = wp_json_encode( $value ); } $context_parts[] = "$key: $value"; } $context_str = ' | ' . implode( ', ', $context_parts ); } $log_entry = sprintf( "[%s] [%s] %s%s\n", $timestamp, $level, $message, $context_str ); // Check file size before writing if ( file_exists( $this->log_file ) && filesize( $this->log_file ) >= $this->max_file_size ) { $this->rotate_log(); } // Write to file return file_put_contents( $this->log_file, $log_entry, FILE_APPEND | LOCK_EX ) !== false; } /** * Read log entries * * @param array $args { * Optional. Array of arguments. * * @type string $level Filter by level (success, error, info, debug). * @type string $date_from Filter from date (Y-m-d). * @type string $date_to Filter to date (Y-m-d). * @type int $limit Maximum number of entries to return. * @type string $search Search term. * } * @return array Array of log entries. */ public function read( $args = array() ) { $defaults = array( 'level' => '', 'date_from' => '', 'date_to' => '', 'limit' => 0, 'search' => '', ); $args = wp_parse_args( $args, $defaults ); if ( ! file_exists( $this->log_file ) ) { return array(); } global $wp_filesystem; if ( empty( $wp_filesystem ) ) { require_once ABSPATH . '/wp-admin/includes/file.php'; WP_Filesystem(); } $log_content = $wp_filesystem->get_contents( $this->log_file ); if ( false === $log_content ) { return array(); } $entries = array(); $lines = explode( "\n", $log_content ); foreach ( $lines as $line ) { $line = trim( $line ); if ( empty( $line ) ) { continue; } $entry = $this->parse_log_line( $line ); if ( ! $entry ) { continue; } // Apply filters if ( ! empty( $args['level'] ) && strtolower( $entry['level'] ) !== strtolower( $args['level'] ) ) { continue; } if ( ! empty( $args['date_from'] ) && $entry['date'] < $args['date_from'] ) { continue; } if ( ! empty( $args['date_to'] ) && $entry['date'] > $args['date_to'] ) { continue; } if ( ! empty( $args['search'] ) && stripos( $entry['message'], $args['search'] ) === false ) { continue; } $entries[] = $entry; } // Reverse to show newest first $entries = array_reverse( $entries ); // Apply limit if ( $args['limit'] > 0 ) { $entries = array_slice( $entries, 0, $args['limit'] ); } return $entries; } /** * Parse log line * * @param string $line Log line. * @return array|false Parsed entry or false on failure. */ private function parse_log_line( $line ) { // Format: [YYYY-MM-DD HH:MM:SS] [LEVEL] Message if ( ! preg_match( '/^\[([\d\-: ]+)\]\s+\[([^\]]+)\]\s+(.+)$/', $line, $matches ) ) { return false; } return array( 'timestamp' => $matches[1], 'date' => substr( $matches[1], 0, 10 ), 'time' => substr( $matches[1], 11 ), 'level' => trim( $matches[2] ), 'message' => trim( $matches[3] ), ); } /** * Clear all logs * * @return bool True on success, false on failure. */ public function clear() { if ( file_exists( $this->log_file ) ) { return wp_delete_file( $this->log_file ); } return true; } /** * Cleanup old log entries * * Removes entries older than retention period * * @return bool True on success, false on failure. */ public function cleanup() { if ( ! file_exists( $this->log_file ) ) { return true; } $cutoff_date = gmdate( 'Y-m-d', strtotime( "-{$this->retention_days} days" ) ); $entries = $this->read(); $new_entries = array(); foreach ( $entries as $entry ) { if ( $entry['date'] >= $cutoff_date ) { $new_entries[] = sprintf( '[%s] [%s] %s', $entry['timestamp'], $entry['level'], $entry['message'] ); } } // Rewrite file with filtered entries if ( empty( $new_entries ) ) { return $this->clear(); } // Reverse back to chronological order $new_entries = array_reverse( $new_entries ); return file_put_contents( $this->log_file, implode( "\n", $new_entries ) . "\n", LOCK_EX ) !== false; } /** * Rotate log file when it exceeds max size * * @return bool True on success, false on failure. */ private function rotate_log() { if ( ! file_exists( $this->log_file ) ) { return true; } // Keep only last 50% of entries $entries = $this->read(); $keep_count = (int) ( count( $entries ) / 2 ); $new_entries = array_slice( $entries, 0, $keep_count ); if ( empty( $new_entries ) ) { return $this->clear(); } $lines = array(); foreach ( array_reverse( $new_entries ) as $entry ) { $lines[] = sprintf( '[%s] [%s] %s', $entry['timestamp'], $entry['level'], $entry['message'] ); } return file_put_contents( $this->log_file, implode( "\n", $lines ) . "\n", LOCK_EX ) !== false; } /** * Get log file size * * @return int File size in bytes. */ public function get_file_size() { if ( file_exists( $this->log_file ) ) { return filesize( $this->log_file ); } return 0; } /** * Get log file path * * @return string Log file path. */ public function get_log_file_path() { return $this->log_file; } /** * Get total log entries count * * @return int Number of log entries. */ public function get_entries_count() { if ( ! file_exists( $this->log_file ) ) { return 0; } global $wp_filesystem; if ( empty( $wp_filesystem ) ) { require_once ABSPATH . '/wp-admin/includes/file.php'; WP_Filesystem(); } $log_content = $wp_filesystem->get_contents( $this->log_file ); if ( false === $log_content ) { return 0; } $lines = explode( "\n", $log_content ); $count = 0; foreach ( $lines as $line ) { $line = trim( $line ); if ( ! empty( $line ) ) { $count++; } } return $count; } /** * Get log statistics * * @return array Statistics array. */ public function get_statistics() { $entries = $this->read(); $stats = array( 'total' => count( $entries ), 'success' => 0, 'error' => 0, 'info' => 0, 'debug' => 0, 'size' => $this->get_file_size(), ); foreach ( $entries as $entry ) { $level = strtolower( $entry['level'] ); if ( isset( $stats[ $level ] ) ) { $stats[ $level ]++; } } return $stats; } /** * Delete log directory and all files * * @return bool True on success, false on failure. */ public function delete_all() { if ( ! file_exists( $this->log_dir ) ) { return true; } global $wp_filesystem; if ( empty( $wp_filesystem ) ) { require_once ABSPATH . '/wp-admin/includes/file.php'; WP_Filesystem(); } // Delete all files in directory $files = glob( trailingslashit( $this->log_dir ) . '*' ); foreach ( $files as $file ) { if ( is_file( $file ) ) { wp_delete_file( $file ); } } // Delete directory return $wp_filesystem->rmdir( $this->log_dir, true ); } }