diff --git a/.gitignore b/.gitignore index 0a930af7e..ac3ff56de 100644 --- a/.gitignore +++ b/.gitignore @@ -500,4 +500,6 @@ build/ .vscode/ contentful/migration-scripts/config.json -contentful/migration-scripts/contentful-export-*.json \ No newline at end of file +contentful/migration-scripts/contentful-export-*.json + +.sonarqube/ \ No newline at end of file diff --git a/src/Dfe.PlanTech.Domain/Content/Interfaces/IHasSlug.cs b/src/Dfe.PlanTech.Domain/Content/Interfaces/IHasSlug.cs index e1ba8a213..44c67d32d 100644 --- a/src/Dfe.PlanTech.Domain/Content/Interfaces/IHasSlug.cs +++ b/src/Dfe.PlanTech.Domain/Content/Interfaces/IHasSlug.cs @@ -2,5 +2,5 @@ namespace Dfe.PlanTech.Domain.Content.Interfaces; public interface IHasSlug { - public string Slug { get; set; } + public string Slug { get; } } diff --git a/src/Dfe.PlanTech.Domain/Content/Interfaces/INavigationLink.cs b/src/Dfe.PlanTech.Domain/Content/Interfaces/INavigationLink.cs index 57724d526..076470dc3 100644 --- a/src/Dfe.PlanTech.Domain/Content/Interfaces/INavigationLink.cs +++ b/src/Dfe.PlanTech.Domain/Content/Interfaces/INavigationLink.cs @@ -26,7 +26,7 @@ public interface INavigationLink /// /// Does this link contain all necessary information (Href + DisplayText)? /// - public bool IsValid => !string.IsNullOrEmpty(DisplayText) && !string.IsNullOrEmpty(Href); + public bool IsValid => !string.IsNullOrEmpty(DisplayText) && !(string.IsNullOrEmpty(Href) && ContentToLinkTo == null); /// /// The content to link to. diff --git a/src/Dfe.PlanTech.Domain/Content/Interfaces/IPage.cs b/src/Dfe.PlanTech.Domain/Content/Interfaces/IPage.cs index 41e0274fe..0e4d22006 100644 --- a/src/Dfe.PlanTech.Domain/Content/Interfaces/IPage.cs +++ b/src/Dfe.PlanTech.Domain/Content/Interfaces/IPage.cs @@ -2,12 +2,10 @@ namespace Dfe.PlanTech.Domain.Content.Interfaces; -public interface IPage +public interface IPage : IHasSlug { public string InternalName { get; } - public string Slug { get; } - public bool DisplayBackButton { get; } public bool DisplayHomeButton { get; } diff --git a/src/Dfe.PlanTech.Infrastructure.Contentful/Persistence/EntityResolver.cs b/src/Dfe.PlanTech.Infrastructure.Contentful/Persistence/EntityResolver.cs index 0bfebc937..00f6c51a4 100644 --- a/src/Dfe.PlanTech.Infrastructure.Contentful/Persistence/EntityResolver.cs +++ b/src/Dfe.PlanTech.Infrastructure.Contentful/Persistence/EntityResolver.cs @@ -1,6 +1,7 @@ using Contentful.Core.Configuration; using Dfe.PlanTech.Domain.Content.Interfaces; using Dfe.PlanTech.Domain.Content.Models; +using Dfe.PlanTech.Domain.Helpers; using Microsoft.Extensions.Logging; namespace Dfe.PlanTech.Infrastructure.Contentful; @@ -15,8 +16,7 @@ public class EntityResolver(ILogger logger) : IContentTypeResolv private readonly ILogger _logger = logger; - private readonly Dictionary _types = typeof(IContentComponent).Assembly.GetTypes() - .Where(type => type.IsAssignableTo(typeof(IContentComponent))) + private readonly Dictionary _types = ReflectionHelpers.GetTypesInheritingFrom() .ToDictionary(type => type.Name.ToLower()); /// diff --git a/src/Dfe.PlanTech.Web/Models/Content/ContentBase.cs b/src/Dfe.PlanTech.Web/Models/Content/ContentBase.cs index 2e5495a15..0633aaa75 100644 --- a/src/Dfe.PlanTech.Web/Models/Content/ContentBase.cs +++ b/src/Dfe.PlanTech.Web/Models/Content/ContentBase.cs @@ -1,12 +1,15 @@ using System.Diagnostics.CodeAnalysis; +using Dfe.PlanTech.Domain.Content.Interfaces; +using Dfe.PlanTech.Domain.Content.Models; namespace Dfe.PlanTech.Web.Models.Content; [ExcludeFromCodeCoverage] -public class ContentBase : Contentful.Core.Models.Entry +public class ContentBase : Contentful.Core.Models.Entry, IContentComponent, IHasSlug { public string InternalName { get; set; } = null!; public string Slug { get; set; } = null!; public string? Title { get; set; } public string? Subtitle { get; set; } + public SystemDetails Sys { get; set; } = null!; } diff --git a/src/Dfe.PlanTech.Web/Models/Content/ContentSupportPage.cs b/src/Dfe.PlanTech.Web/Models/Content/ContentSupportPage.cs index 1f65da2d3..1c4414273 100644 --- a/src/Dfe.PlanTech.Web/Models/Content/ContentSupportPage.cs +++ b/src/Dfe.PlanTech.Web/Models/Content/ContentSupportPage.cs @@ -3,7 +3,7 @@ namespace Dfe.PlanTech.Web.Models.Content; [ExcludeFromCodeCoverage] -public class ContentSupportPage : ContentBase +public class ContentSupportPage : ContentBase, IContentSupportPage { public Heading Heading { get; init; } = null!; public List Content { get; init; } = []; @@ -13,5 +13,4 @@ public class ContentSupportPage : ContentBase public bool HasFeedbackBanner { get; init; } public bool HasPrint { get; init; } public bool ShowVerticalNavigation { get; init; } - } diff --git a/src/Dfe.PlanTech.Web/Models/Content/IContentSupportPage.cs b/src/Dfe.PlanTech.Web/Models/Content/IContentSupportPage.cs new file mode 100644 index 000000000..5082b41a6 --- /dev/null +++ b/src/Dfe.PlanTech.Web/Models/Content/IContentSupportPage.cs @@ -0,0 +1,20 @@ +using Dfe.PlanTech.Domain.Content.Interfaces; + +namespace Dfe.PlanTech.Web.Models.Content; + +public interface IContentSupportPage : IContentSupportPage +where TContent : class +{ + List Content { get; } +} + +public interface IContentSupportPage : IContentComponent, IHasSlug +{ + Heading Heading { get; } + bool IsSitemap { get; } + bool HasCitation { get; } + bool HasBackToTop { get; } + bool HasFeedbackBanner { get; } + bool HasPrint { get; } + bool ShowVerticalNavigation { get; } +} diff --git a/src/Dfe.PlanTech.Web/Models/Content/Mapped/CsPage.cs b/src/Dfe.PlanTech.Web/Models/Content/Mapped/CsPage.cs index 80b547cda..4dc84f9c2 100644 --- a/src/Dfe.PlanTech.Web/Models/Content/Mapped/CsPage.cs +++ b/src/Dfe.PlanTech.Web/Models/Content/Mapped/CsPage.cs @@ -1,10 +1,12 @@ using System.Diagnostics.CodeAnalysis; +using Dfe.PlanTech.Domain.Content.Models; namespace Dfe.PlanTech.Web.Models.Content.Mapped; [ExcludeFromCodeCoverage] -public class CsPage +public class CsPage : IContentSupportPage { + public SystemDetails Sys { get; set; } = null!; public Heading Heading { get; set; } = null!; public string Slug { get; set; } = null!; public bool IsSitemap { get; set; } diff --git a/src/Dfe.PlanTech.Web/TagHelpers/FooterLinkTagHelper.cs b/src/Dfe.PlanTech.Web/TagHelpers/FooterLinkTagHelper.cs index 46e6e44e6..de296991a 100644 --- a/src/Dfe.PlanTech.Web/TagHelpers/FooterLinkTagHelper.cs +++ b/src/Dfe.PlanTech.Web/TagHelpers/FooterLinkTagHelper.cs @@ -1,8 +1,6 @@ -using System.Text; using Dfe.PlanTech.Domain.Content.Interfaces; using Dfe.PlanTech.Domain.Content.Models; using Dfe.PlanTech.Web.Models.Content; -using Dfe.PlanTech.Web.Models.Content.Mapped; using Microsoft.AspNetCore.Razor.TagHelpers; namespace Dfe.PlanTech.Web.TagHelpers; @@ -17,46 +15,41 @@ public class FooterLinkTagHelper(ILogger logger) : TagHelpe public override void Process(TagHelperContext context, TagHelperOutput output) { - if (Link == null || !Link.IsValid) + if (Link == null || !Link.IsValid || !TryBuildElement(output)) { - logger.LogWarning("Missing {link}", nameof(Link)); + output.TagName = null; + output.Content.SetHtmlContent(""); + logger.LogWarning("Missing or invalid {Name} {Link}", nameof(NavigationLink), Link); return; } - var html = GetHtml(); - - output.TagName = null; + output.TagName = "a"; output.TagMode = TagMode.StartTagAndEndTag; - output.Content.SetHtmlContent(html); } - public string GetHtml() + /// + /// Adds attributes to element + /// + /// + /// True; valid element. False; invalid + public bool TryBuildElement(TagHelperOutput output) { - var stringBuilder = new StringBuilder(); - AppendOpenTag(stringBuilder); - stringBuilder.Append(Link!.DisplayText); - AppendCloseTag(stringBuilder); - - return stringBuilder.ToString(); - } + var href = GetHref(); - private static void AppendCloseTag(StringBuilder stringBuilder) - { - stringBuilder.Append(""); - } + if (string.IsNullOrEmpty(href)) + { + return false; + } - private void AppendOpenTag(StringBuilder stringBuilder) - { - stringBuilder.Append("""'); + return true; } private string GetHref() @@ -77,18 +70,29 @@ private string GetHref() return null; } + if (Link.ContentToLinkTo is not IHasSlug hasSlug) + { + logger.LogError("Invalid content type received for Link. Expected {Interface} but type is {Concrete}", typeof(IHasSlug), Link.ContentToLinkTo.GetType()); + return null; + } + + var firstCharacterIsSlash = hasSlug.Slug[0] == '/'; + + var slug = firstCharacterIsSlash ? hasSlug.Slug.AsSpan(1) : hasSlug.Slug.AsSpan(); + return Link.ContentToLinkTo switch { - IPage page => $"/{page.Slug}", - CsPage csPage => $"/content/{csPage.Slug}", - ContentSupportPage contentSupportPage => $"/content/{contentSupportPage.Slug}", + IPage => $"/{slug}", + IContentSupportPage => $"/content/{slug}", _ => LogInvalidContentTypeAndReturnNull(Link.ContentToLinkTo) }; } + + private string? LogInvalidContentTypeAndReturnNull(object content) { - logger.LogError("Unsupported content type {ContentType} in {TagHelper}", + logger.LogError("Unsupported content type {ContentType} in {TagHelper}", content.GetType().Name, nameof(FooterLinkTagHelper)); return null; diff --git a/src/Dfe.PlanTech.Web/Views/Shared/Components/FooterLinks/Default.cshtml b/src/Dfe.PlanTech.Web/Views/Shared/Components/FooterLinks/Default.cshtml index 8a660e5b8..532971555 100644 --- a/src/Dfe.PlanTech.Web/Views/Shared/Components/FooterLinks/Default.cshtml +++ b/src/Dfe.PlanTech.Web/Views/Shared/Components/FooterLinks/Default.cshtml @@ -2,10 +2,10 @@