using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Hosting; using Oqtane.Shared; using Oqtane.Enums; using Oqtane.Infrastructure; using Oqtane.Controllers; using SZUAbsolventenverein.Module.HallOfFame.Services; using System.Linq; using System.Collections.Generic; using System.Threading.Tasks; using System.Text.RegularExpressions; using System.Net; using QuestPDF.Fluent; using QuestPDF.Helpers; using QuestPDF.Infrastructure; namespace SZUAbsolventenverein.Module.HallOfFame.Controllers { [Route(ControllerRoutes.ApiRoute)] public class HallOfFamePdfController : ModuleControllerBase { private readonly IHallOfFameService _hallOfFameService; private readonly IWebHostEnvironment _environment; public HallOfFamePdfController(IHallOfFameService hallOfFameService, ILogManager logger, IHttpContextAccessor accessor, IWebHostEnvironment environment) : base(logger, accessor) { _hallOfFameService = hallOfFameService; _environment = environment; } // GET: api/?moduleid=x&download=true/false&id=y [HttpGet] [Authorize(Policy = PolicyNames.ViewModule)] public async Task Get(string moduleid, bool download = false, int? id = null) { int ModuleId; if (int.TryParse(moduleid, out ModuleId) && IsAuthorizedEntityId(EntityNames.Module, ModuleId)) { var entries = await _hallOfFameService.GetHallOfFamesAsync(ModuleId); var publishedEntries = entries.Where(e => e.Status == "Published").ToList(); // If a specific entry ID is provided, filter to just that entry if (id.HasValue) { publishedEntries = publishedEntries.Where(e => e.HallOfFameId == id.Value).ToList(); if (!publishedEntries.Any()) { return NotFound(); } } var document = Document.Create(container => { foreach (var entry in publishedEntries) { // Bild laden falls vorhanden byte[] imageBytes = null; if (!string.IsNullOrEmpty(entry.Image)) { try { var fullImagePath = System.IO.Path.Combine( _environment.WebRootPath, entry.Image.TrimStart('/')); if (System.IO.File.Exists(fullImagePath)) imageBytes = System.IO.File.ReadAllBytes(fullImagePath); } catch { } } container.Page(page => { page.Size(PageSizes.A4); page.Margin(0); page.Content().Layers(layers => { // ── Hintergrundbild (Edge-to-Edge) ── if (imageBytes != null) { layers.Layer().Image(imageBytes).FitUnproportionally(); } else { layers.Layer().Background("#1A1A2E"); } // ── Inhalt (PrimaryLayer) ── layers.PrimaryLayer() .Padding(40) .Column(column => { // ═══ TITELKARTE (oben) ═══ column.Item() .Border(5f).BorderColor("#20000000") .CornerRadius(24) .Border(3f).BorderColor("#33000000") .CornerRadius(22) .Border(1f).BorderColor("#44FFFFFF") .Background("#CC1A1A2E") .CornerRadius(20) .PaddingVertical(28) .PaddingHorizontal(36) .Column(inner => { // Name: groß, dominant, Uppercase inner.Item() .PaddingBottom(6) .Text(entry.Name.ToUpper()) .FontSize(36) .ExtraBold() .FontColor(Colors.White) .LetterSpacing(0.5f); // Trennlinie inner.Item() .PaddingVertical(8) .Height(1.5f) .Background("#55FFFFFF"); // Jahr: sekundär, elegant inner.Item() .PaddingTop(4) .Text($"Jahrgang {entry.Year}") .FontSize(15) .FontColor("#CCFFFFFF") .LetterSpacing(1.5f); }); // ═══ BESCHREIBUNGSKARTE (unten) ═══ var sections = ConvertHtmlToLines(entry.Description ?? ""); column.Item().ExtendVertical().AlignBottom() .Border(5f).BorderColor("#20000000") .CornerRadius(20) .Border(3f).BorderColor("#33000000") .CornerRadius(18) .Border(1f).BorderColor("#33FFFFFF") .Background("#CC1A1A2E") .CornerRadius(16) .PaddingVertical(24) .PaddingHorizontal(32) .Column(descColumn => { if (sections.Count > 0) { // Überschrift descColumn.Item() .PaddingBottom(12) .Text("Beschreibung") .FontSize(14) .SemiBold() .FontColor("#B0FFFFFF") .LetterSpacing(2f); // Trennlinie descColumn.Item() .PaddingBottom(14) .Height(1f) .Background("#33FFFFFF"); // Text foreach (var line in sections) { descColumn.Item() .PaddingBottom(8) .Text(line) .FontSize(11) .FontColor("#E8FFFFFF") .LineHeight(1.5f); } } }); }); }); }); } }); byte[] pdfBytes = document.GeneratePdf(); if (download) { return File(pdfBytes, "application/pdf", "HallOfFame.pdf"); } // Inline: PDF wird im Browser angezeigt (Vorschau) return File(pdfBytes, "application/pdf"); } else { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized HallOfFame PDF Get Attempt {ModuleId}", moduleid); return Forbid(); } } /// /// Converts HTML content (from Quill.js rich text editor) to a list of plain text lines /// suitable for rendering in QuestPDF. /// Handles: ol/ul lists → numbered/bulleted lines, p → paragraphs, br → newlines, /// strips all other tags and decodes HTML entities. /// private static List ConvertHtmlToLines(string html) { if (string.IsNullOrWhiteSpace(html)) return new List(); var lines = new List(); // Process ordered lists:
    ...
html = Regex.Replace(html, @"]*>(.*?)", match => { var listContent = match.Groups[1].Value; var items = Regex.Matches(listContent, @"]*>(.*?)", RegexOptions.Singleline); int counter = 1; var result = ""; foreach (Match item in items) { var text = StripHtmlTags(item.Groups[1].Value).Trim(); if (!string.IsNullOrEmpty(text)) result += $"\n{counter}. {text}"; counter++; } return result; }, RegexOptions.Singleline | RegexOptions.IgnoreCase); // Process unordered lists:
    ...
html = Regex.Replace(html, @"]*>(.*?)", match => { var listContent = match.Groups[1].Value; var items = Regex.Matches(listContent, @"]*>(.*?)", RegexOptions.Singleline); var result = ""; foreach (Match item in items) { var text = StripHtmlTags(item.Groups[1].Value).Trim(); if (!string.IsNullOrEmpty(text)) result += $"\n• {text}"; } return result; }, RegexOptions.Singleline | RegexOptions.IgnoreCase); // Replace
,
,
with newline html = Regex.Replace(html, @"", "\n", RegexOptions.IgnoreCase); // Replace

, , - with newline html = Regex.Replace(html, @"", "\n", RegexOptions.IgnoreCase); // Strip all remaining HTML tags html = StripHtmlTags(html); // Decode HTML entities (  & etc.) html = WebUtility.HtmlDecode(html); // Split into lines, trim, filter empty var rawLines = html.Split('\n') .Select(s => s.Trim()) .Where(s => !string.IsNullOrEmpty(s)) .ToList(); return rawLines; } /// /// Strips all HTML tags from a string, leaving only the text content. /// private static string StripHtmlTags(string html) { return Regex.Replace(html, @"<[^>]+>", ""); } } }