using System.IO; using System.Text; using SharepointToolbox.Core.Models; namespace SharepointToolbox.Services.Export; /// /// Exports permission entries to a self-contained interactive HTML report. /// Ports PowerShell Export-PermissionsToHTML functionality. /// No external CSS/JS dependencies — everything is inline. /// public class HtmlExportService { /// /// Builds a self-contained HTML string from the supplied permission entries. /// Includes inline CSS, inline JS filter, stats cards, type badges, unique/inherited badges, and user pills. /// public string BuildHtml(IReadOnlyList entries, ReportBranding? branding = null) { // Compute stats var totalEntries = entries.Count; var uniquePermSets = entries.Select(e => e.PermissionLevels).Distinct().Count(); var distinctUsers = entries .SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries)) .Select(u => u.Trim()) .Where(u => u.Length > 0) .Distinct() .Count(); var sb = new StringBuilder(); // ── HTML HEAD ────────────────────────────────────────────────────────── sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine("SharePoint Permissions Report"); sb.AppendLine(""); sb.AppendLine(""); // ── BODY ─────────────────────────────────────────────────────────────── sb.AppendLine(""); sb.Append(BrandingHtmlHelper.BuildBrandingHeader(branding)); sb.AppendLine("

SharePoint Permissions Report

"); // Stats cards sb.AppendLine("
"); sb.AppendLine($"
{totalEntries}
Total Entries
"); sb.AppendLine($"
{uniquePermSets}
Unique Permission Sets
"); sb.AppendLine($"
{distinctUsers}
Distinct Users/Groups
"); sb.AppendLine("
"); // Filter input sb.AppendLine("
"); sb.AppendLine(" "); sb.AppendLine("
"); // Table sb.AppendLine("
"); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(" "); sb.AppendLine(""); sb.AppendLine(""); foreach (var entry in entries) { var typeCss = ObjectTypeCss(entry.ObjectType); var uniqueCss = entry.HasUniquePermissions ? "badge unique" : "badge inherited"; var uniqueLbl = entry.HasUniquePermissions ? "Unique" : "Inherited"; // Build user pills: zip UserLogins and Users (both semicolon-delimited) var logins = entry.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries); var names = entry.Users.Split(';', StringSplitOptions.RemoveEmptyEntries); var pillsBuilder = new StringBuilder(); for (int i = 0; i < logins.Length; i++) { var login = logins[i].Trim(); var name = i < names.Length ? names[i].Trim() : login; var isExt = login.Contains("#EXT#", StringComparison.OrdinalIgnoreCase); var pillCss = isExt ? "user-pill external-user" : "user-pill"; pillsBuilder.Append($"{HtmlEncode(name)}"); } sb.AppendLine(""); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine(""); } sb.AppendLine(""); sb.AppendLine("
ObjectTitleURLUniqueUsers/GroupsPermission LevelGranted Through
{HtmlEncode(entry.ObjectType)}{HtmlEncode(entry.Title)}Link{uniqueLbl}{pillsBuilder}{HtmlEncode(entry.PermissionLevels)}{HtmlEncode(entry.GrantedThrough)}
"); sb.AppendLine("
"); // Inline JS sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); return sb.ToString(); } /// /// Writes the HTML report to the specified file path using UTF-8 without BOM. /// public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct, ReportBranding? branding = null) { var html = BuildHtml(entries, branding); await File.WriteAllTextAsync(filePath, html, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), ct); } /// Returns inline CSS background and text color for a risk level. private static (string bg, string text, string border) RiskLevelColors(RiskLevel level) => level switch { RiskLevel.High => ("#FEE2E2", "#991B1B", "#FECACA"), RiskLevel.Medium => ("#FEF3C7", "#92400E", "#FDE68A"), RiskLevel.Low => ("#D1FAE5", "#065F46", "#A7F3D0"), RiskLevel.ReadOnly => ("#DBEAFE", "#1E40AF", "#BFDBFE"), _ => ("#F3F4F6", "#374151", "#E5E7EB") }; /// /// Builds a self-contained HTML string from simplified permission entries. /// Includes risk-level summary cards, color-coded rows, and simplified labels column. /// public string BuildHtml(IReadOnlyList entries, ReportBranding? branding = null) { var summaries = PermissionSummaryBuilder.Build(entries); var totalEntries = entries.Count; var uniquePermSets = entries.Select(e => e.PermissionLevels).Distinct().Count(); var distinctUsers = entries .SelectMany(e => e.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries)) .Select(u => u.Trim()) .Where(u => u.Length > 0) .Distinct() .Count(); var sb = new StringBuilder(); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine("SharePoint Permissions Report (Simplified)"); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); sb.Append(BrandingHtmlHelper.BuildBrandingHeader(branding)); sb.AppendLine("

SharePoint Permissions Report (Simplified)

"); // Stats cards sb.AppendLine("
"); sb.AppendLine($"
{totalEntries}
Total Entries
"); sb.AppendLine($"
{uniquePermSets}
Unique Permission Sets
"); sb.AppendLine($"
{distinctUsers}
Distinct Users/Groups
"); sb.AppendLine("
"); // Risk-level summary cards sb.AppendLine("
"); foreach (var summary in summaries) { var (bg, text, border) = RiskLevelColors(summary.RiskLevel); sb.AppendLine($"
"); sb.AppendLine($"
{summary.Count}
"); sb.AppendLine($"
{HtmlEncode(summary.Label)}
"); sb.AppendLine($"
{summary.DistinctUsers} user(s)
"); sb.AppendLine("
"); } sb.AppendLine("
"); // Filter input sb.AppendLine("
"); sb.AppendLine(" "); sb.AppendLine("
"); // Table with simplified columns sb.AppendLine("
"); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(" "); sb.AppendLine(""); sb.AppendLine(""); foreach (var entry in entries) { var typeCss = ObjectTypeCss(entry.ObjectType); var uniqueCss = entry.HasUniquePermissions ? "badge unique" : "badge inherited"; var uniqueLbl = entry.HasUniquePermissions ? "Unique" : "Inherited"; var (riskBg, riskText, riskBorder) = RiskLevelColors(entry.RiskLevel); var logins = entry.UserLogins.Split(';', StringSplitOptions.RemoveEmptyEntries); var names = entry.Inner.Users.Split(';', StringSplitOptions.RemoveEmptyEntries); var pillsBuilder = new StringBuilder(); for (int i = 0; i < logins.Length; i++) { var login = logins[i].Trim(); var name = i < names.Length ? names[i].Trim() : login; var isExt = login.Contains("#EXT#", StringComparison.OrdinalIgnoreCase); var pillCss = isExt ? "user-pill external-user" : "user-pill"; pillsBuilder.Append($"{HtmlEncode(name)}"); } sb.AppendLine(""); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine($" "); sb.AppendLine(""); } sb.AppendLine(""); sb.AppendLine("
ObjectTitleURLUniqueUsers/GroupsPermission LevelSimplifiedRiskGranted Through
{HtmlEncode(entry.ObjectType)}{HtmlEncode(entry.Title)}Link{uniqueLbl}{pillsBuilder}{HtmlEncode(entry.PermissionLevels)}{HtmlEncode(entry.SimplifiedLabels)}{HtmlEncode(entry.RiskLevel.ToString())}{HtmlEncode(entry.GrantedThrough)}
"); sb.AppendLine("
"); sb.AppendLine(""); sb.AppendLine(""); sb.AppendLine(""); return sb.ToString(); } /// /// Writes the simplified HTML report to the specified file path. /// public async Task WriteAsync(IReadOnlyList entries, string filePath, CancellationToken ct, ReportBranding? branding = null) { var html = BuildHtml(entries, branding); await File.WriteAllTextAsync(filePath, html, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), ct); } /// Returns the CSS class for the object-type badge. private static string ObjectTypeCss(string t) => t switch { "Site Collection" => "badge site-coll", "Site" => "badge site", "List" => "badge list", "Folder" => "badge folder", _ => "badge" }; /// Minimal HTML encoding for text content and attribute values. private static string HtmlEncode(string value) { if (string.IsNullOrEmpty(value)) return string.Empty; return value .Replace("&", "&") .Replace("<", "<") .Replace(">", ">") .Replace("\"", """) .Replace("'", "'"); } }