Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
40a4f90
Reduce compilation targets and reconfigure for CMS 13
GeekInTheNorth Jan 23, 2026
6ea3934
Handle switch from ISiteDefinitionRepository to IApplicationRepository
GeekInTheNorth Jan 27, 2026
4eb18c2
Replace IPageRouteHelper with IContentRouteHelper
GeekInTheNorth Jan 27, 2026
9e70a25
Replace plugin attributes with their new named attributes.
GeekInTheNorth Jan 27, 2026
c455160
Adding claude code context and actioning unit test suggestions from c…
GeekInTheNorth Feb 2, 2026
ba15f7e
Add Audit Cleanup Job
GeekInTheNorth Feb 2, 2026
ee6010f
Initial development of Custom Headers by Claude followed by review an…
GeekInTheNorth Feb 4, 2026
f2a6684
Fix rendering of the custom headers
GeekInTheNorth Feb 4, 2026
3b22ffa
Fix security.txt container
GeekInTheNorth Feb 4, 2026
90affae
Include all standard security headers into the custom headers interface.
GeekInTheNorth Feb 4, 2026
f336e0a
Remove deprecated Security Headers code and rename Custom Headers as …
GeekInTheNorth Feb 5, 2026
6942d54
Convert to use a card UI
GeekInTheNorth Feb 5, 2026
81f0b11
Switch to a card based layout for custom headers.
GeekInTheNorth Feb 5, 2026
4361a0c
Consolidate delete confirmation modals
GeekInTheNorth Feb 5, 2026
f179331
Update CLAUDE.md to reflect changes in testing, project structure, an…
GeekInTheNorth Feb 6, 2026
e282b70
Add Custom Headers to the export
GeekInTheNorth Feb 6, 2026
5d753b2
Add Custom Headers to the import
GeekInTheNorth Feb 6, 2026
eb0d7e1
Add unit tests for the custom headers feature.
GeekInTheNorth Feb 9, 2026
4092ca4
Merge branch 'develop' into feature/cms13
GeekInTheNorth Feb 15, 2026
caa300d
Merge branch 'develop' into feature/cms13
GeekInTheNorth Feb 15, 2026
0a7b042
Fix unit tests
GeekInTheNorth Feb 15, 2026
8d6598b
Merge branch 'feature/cms13' into feature/custom_headers_with_claude
GeekInTheNorth Feb 16, 2026
fdb9695
Initial wiring up of custom headers migration code.
GeekInTheNorth Feb 18, 2026
fd8088e
Merge pull request #349 from GeekInTheNorth/feature/custom_headers_wi…
GeekInTheNorth Feb 18, 2026
3c57bd2
Tidy up Custom Headers migration.
GeekInTheNorth Feb 26, 2026
ebf019c
PR Changes for custom headers
GeekInTheNorth Feb 26, 2026
33aa8bf
CMS 13 preview and setting up of experience pages.
GeekInTheNorth Mar 2, 2026
644f2a0
Add a Listing Page for demo variance.
GeekInTheNorth Mar 3, 2026
f60a36f
Merge branch 'main' into feature/cms13
GeekInTheNorth Mar 5, 2026
dac1fb9
Merge pull request #357 from GeekInTheNorth/main
GeekInTheNorth Mar 5, 2026
e452b1b
Merge branch 'develop' into feature/cms13
GeekInTheNorth Mar 5, 2026
f5e5730
Remove duplicate methods that cause a build failure.
GeekInTheNorth Mar 6, 2026
41a0215
Correct sites to applications and rebuild UI
GeekInTheNorth Mar 12, 2026
2ae5782
Alpha Build
GeekInTheNorth Mar 12, 2026
1d0c861
Clean up settings
GeekInTheNorth Mar 12, 2026
3d50e3a
Initial Implementation With Claude
GeekInTheNorth Mar 16, 2026
fee1509
Remove appsettings.Development.json
GeekInTheNorth Mar 16, 2026
7a0de8e
Fix up migration and Context switcher ui
GeekInTheNorth Mar 16, 2026
f56e635
Minor tweaks to cache keys and context switcher
GeekInTheNorth Mar 16, 2026
e973946
Update sample site setup task to create 2 sites both with an Edit and…
GeekInTheNorth Mar 18, 2026
6c51115
Update Context Switcher for language and additional host information.
GeekInTheNorth Mar 18, 2026
f917849
Update logic for resolving settings, sandbox, sources and nonces.
GeekInTheNorth Mar 19, 2026
6698002
Make the text dark on the overridden context.
GeekInTheNorth Mar 19, 2026
3a8498d
Use sanitized host name on all entry points
GeekInTheNorth Mar 19, 2026
e923139
Allow Permission Policy to vary by app or host
GeekInTheNorth Mar 19, 2026
4ebce90
Restrict permissions policy UI so a conscious action to override inhe…
GeekInTheNorth Mar 20, 2026
d5c8f74
Reduce number of potential DB queries when resolving permission polic…
GeekInTheNorth Mar 23, 2026
2b9d648
Initial Conversion of Custom Headers to use context specific settings
GeekInTheNorth Mar 23, 2026
98cb4f5
Update List method test to use string literal for behavior filter
GeekInTheNorth Mar 23, 2026
15304d7
Merge Migrations
GeekInTheNorth Mar 23, 2026
576d32d
Update Preview Screen to handle AppId and HostName.
GeekInTheNorth Mar 24, 2026
f0f7512
Initial claude changes for the import / export functionality.
GeekInTheNorth Mar 24, 2026
83f885c
Clean up behaviours and streamline override behaviours for the permis…
GeekInTheNorth Mar 26, 2026
5f26c69
Tidy up around custom headers
GeekInTheNorth Mar 26, 2026
cef719b
Functional and cosmetic updates for the import/export tools.
GeekInTheNorth Mar 26, 2026
e13af37
Use NoTracking for performance
GeekInTheNorth Mar 26, 2026
c9f0955
Remove Migration steps that set up default data and convert the nonce…
GeekInTheNorth Mar 26, 2026
1c0696a
Correct UX bugs relating to context status.
GeekInTheNorth Mar 26, 2026
86b932c
Fix PR Issues
GeekInTheNorth Mar 27, 2026
9c0c41d
Merge pull request #363 from GeekInTheNorth/feature/multi-site-support
GeekInTheNorth Mar 27, 2026
ac42b28
Update build to target CMS Preview 4
GeekInTheNorth Mar 29, 2026
adde320
Update alpha release package
GeekInTheNorth Mar 29, 2026
24c5b75
Separate CMS 13 data stores from legacy CMS 12 data stores to avoid c…
GeekInTheNorth Mar 30, 2026
16648b5
Correct minor bugs
GeekInTheNorth Mar 30, 2026
dba53de
Move EF designer dependency to be debug only.
GeekInTheNorth Mar 30, 2026
a9c83ea
Merge pull request #365 from GeekInTheNorth/feature/rename_context
GeekInTheNorth Mar 30, 2026
ac9da92
Reduce the size of the context switcher in terms of size and hide hos…
GeekInTheNorth Mar 30, 2026
8ca30d7
Update documentation.
GeekInTheNorth Mar 31, 2026
0f16190
Refactor Security.txt management to work with the Context Selector an…
GeekInTheNorth Apr 1, 2026
b823e1b
Compile for the release version of CMS 13
GeekInTheNorth Apr 1, 2026
9407d09
Improve use of caching with shorter keys and more aggressive caching.
GeekInTheNorth Apr 1, 2026
169d712
Remove default source entry
GeekInTheNorth Apr 1, 2026
aad67f4
Remove tests that break following removal of the default 'self' source
GeekInTheNorth Apr 1, 2026
0128997
Show all CSP sources
GeekInTheNorth Apr 6, 2026
f97bc2d
Update source list hints
GeekInTheNorth Apr 7, 2026
60a4b57
Package for release
GeekInTheNorth Apr 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -360,3 +360,9 @@ MigrationBackup/
.mono
.DS_store
.vscode/

# AI
.claude/

# Settings
appsettings.Development.json
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -771,4 +771,4 @@ Before working on this codebase, verify understanding of:
---

*Last Updated: 2026-02-06*
*Generated for: Optimizely CMS 13 / .NET 10 / React 19*
*Generated for: Optimizely CMS 13 / .NET 10 / React 19*
Binary file modified Images/AuditTab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Images/ContextSwitcher.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Images/CspSettingsTab-2A.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Images/CspSettingsTab-2B.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Images/CspViolationTab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Images/PermissionPolicy.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Images/PreviewTab.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Images/ResponseHeaders.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified Images/TabList.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 73 additions & 31 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
DisplayName = "Embed Block",
GUID = "947e1f1d-54ad-4f3b-ba80-3741d2523ea7",
Description = "",
GroupName = SystemTabNames.Content)]
GroupName = SystemTabNames.Content,
CompositionBehaviors = [CompositionBehavior.ElementEnabledKey])]
public class EmbedBlock : BlockData
{
[CultureSpecific]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
namespace OptimizelyTwelveTest.Features.Blocks.RichText
namespace OptimizelyTwelveTest.Features.Blocks.RichText;

using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using System.ComponentModel.DataAnnotations;

[ContentType(
DisplayName = "Rich Text Block",
GUID = "ad51630a-45a9-40d6-ba24-1817e7c3cdd7",
Description = "",
GroupName = SystemTabNames.Content,
CompositionBehaviors = [CompositionBehavior.ElementEnabledKey, CompositionBehavior.SectionEnabledKey])]
public class RichTextBlock : BlockData
{
using EPiServer.Core;
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using System.ComponentModel.DataAnnotations;
[Display(Name = "Css Class", Order = 2)]
public virtual string CustomClass { get; set; }

[ContentType(
DisplayName = "Rich Text Block",
GUID = "ad51630a-45a9-40d6-ba24-1817e7c3cdd7",
Description = "",
GroupName = SystemTabNames.Content)]
public class RichTextBlock : BlockData
{
[CultureSpecific]
[Display(
Name = "Content",
Description = "Rich Text Content",
GroupName = SystemTabNames.Content,
Order = 1)]
public virtual XhtmlString Content { get; set; }
}
[CultureSpecific]
[Display(
Name = "Content",
Description = "Rich Text Content",
GroupName = SystemTabNames.Content,
Order = 1)]
public virtual XhtmlString Content { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using EPiServer.DataAbstraction;
using EPiServer.DataAnnotations;
using EPiServer.VisualBuilder;

namespace OptimizelyTwelveTest.Features.Blocks.Sections;

[ContentType(DisplayName = "Standard Section", GUID = "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d", Description = "A standard section for campaign pages.")]
public class StandardSection : SectionData
{
public override void SetDefaultValues(ContentType contentType)
{
base.SetDefaultValues(contentType);
// Layout.Type should be "grid" for the grid/row/column tag helpers
Layout = new Layout("grid");
var row = new LayoutStructureNode("row");

var left = new LayoutStructureNode("column");
left.DisplaySettings["col"] = "col-md-9";

var right = new LayoutStructureNode("column");
right.DisplaySettings["col"] = "col-md-3";

row.Nodes.Add(left);
row.Nodes.Add(right);

Layout.Nodes.Add(row);
}
}
9 changes: 9 additions & 0 deletions Sample/OptimizelyTwelveTest/Features/Campaign/CampaignPage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using EPiServer.DataAnnotations;
using OptimizelyTwelveTest.Features.Common.Pages;

namespace OptimizelyTwelveTest.Features.Campaign;

[ContentType(DisplayName = "Campaign Page", GUID = "d1b9c8e5-9f3a-4c2b-8e5a-1f2b3c4d5e6f", Description = "A page type for creating rich campaigns.")]
public class CampaignPage : SiteExperienceData
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
using OptimizelyTwelveTest.Features.Common;
using OptimizelyTwelveTest.Features.Security;

namespace OptimizelyTwelveTest.Features.Campaign;

public class CampaignPageController : PageControllerBase<CampaignPage>
{
[SecurityHeaderAction]
public IActionResult Index(CampaignPage currentContent)
{
var model = new CampaignPageViewModel { CurrentPage = currentContent };

return View(model);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using OptimizelyTwelveTest.Features.Common.Pages;

namespace OptimizelyTwelveTest.Features.Campaign;

public class CampaignPageViewModel : ISitePageViewModel<CampaignPage>
{
public CampaignPage CurrentPage { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
{
using EPiServer.Core;

public interface ISitePageData
public interface ISitePageData : IContent
{
string TeaserTitle { get; }

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
namespace OptimizelyTwelveTest.Features.Common.Pages
{
using EPiServer.Core;
using EPiServer.VisualBuilder;
using EPiServer.Web;

using System.ComponentModel.DataAnnotations;

public class SiteExperienceData : ExperienceData, ISitePageData
{
[Display(
Name = "Teaser Title",
Description = "A teaser title to use when rendering as a block",
GroupName = GroupNames.Teaser,
Order = 700)]
public virtual string TeaserTitle { get; set; }

[Display(
Name = "Teaser Text",
Description = "Teaser text to use when rendering as a block",
GroupName = GroupNames.Teaser,
Order = 701)]
public virtual string TeaserText { get; set; }

[Display(
Name = "Teaser Image",
Description = "The image to use when rendering as a block",
GroupName = GroupNames.Teaser,
Order = 702)]
[UIHint(UIHint.Image)]
public virtual ContentReference TeaserImage { get; set; }

[Display(
Name = "Meta Title",
Description = "A meta title to be rendered within the page's title",
GroupName = GroupNames.SearchEngineOptimization,
Order = 800)]
public virtual string MetaTitle { get; set; }

[Display(
Name = "Meta Description",
Description = "The meta description of the page to be shown in search results.",
GroupName = GroupNames.SearchEngineOptimization,
Order = 801)]
public virtual string MetaText { get; set; }

[Display(
Name = "Meta Image",
Description = "The image to use for the meta image for the page",
GroupName = GroupNames.SearchEngineOptimization,
Order = 802)]
[UIHint(UIHint.Image)]
public virtual ContentReference MetaImage { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
using System.Linq;

using EPiServer;
using EPiServer.Applications;
using EPiServer.Core;
using EPiServer.DataAbstraction.Migration;
using EPiServer.DataAccess;
using EPiServer.Security;
using EPiServer.ServiceLocation;
using EPiServer.Web;

using OptimizelyTwelveTest.Features.Home;
using OptimizelyTwelveTest.Features.NotFound;
Expand All @@ -22,46 +22,106 @@ public override void AddChanges()
{
try
{
SetUpSystem();
var appRepository = ServiceLocator.Current.GetInstance<IApplicationRepository>();
if (!ConfigurationExists(appRepository))
{
var contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();
SetUpSystem(appRepository, contentRepository, 1, 5000, 5001);
SetUpSystem(appRepository, contentRepository, 2, 5002, 5003);
SetUpHeadlessSystem(appRepository, contentRepository, 3, 5004, 5005);
}
}
catch(Exception ex)
{
Console.WriteLine($"Error encountered during initial configuration: {ex.Message}");
}
}

private void SetUpSystem()
private static bool ConfigurationExists(IApplicationRepository appRepository)
{
var siteRepository = ServiceLocator.Current.GetInstance<ISiteDefinitionRepository>();
var sites = siteRepository.List();
if (sites.Any())
var sites = appRepository.List();
return sites.Any();
}

private static void SetUpSystem(IApplicationRepository appRepository, IContentRepository contentRepository, int siteNumber, int primaryPort, int cmsPort)
{
var culture = new CultureInfo("en");

// Create HomePage
var newHomePage = contentRepository.GetDefault<HomePage>(ContentReference.RootPage, culture);
newHomePage.Name = $"Home {siteNumber}";
newHomePage.Heading = $"Home {siteNumber}";
newHomePage.MetaTitle = $"Home {siteNumber}";

var homePageReference = contentRepository.Save(newHomePage, SaveAction.Publish, AccessLevel.NoAccess);

// Create Not Found Page
var newNotFoundPage = contentRepository.GetDefault<NotFoundPage>(homePageReference, culture);
newNotFoundPage.Name = $"Not Found {siteNumber}";
newNotFoundPage.MetaTitle = $"Not Found {siteNumber}";

var notFoundReference = contentRepository.Save(newNotFoundPage, SaveAction.Publish, AccessLevel.NoAccess);

// Create SiteSettings
var newSiteSettings = contentRepository.GetDefault<SiteSettingsPage>(homePageReference, culture);
newSiteSettings.Name = $"[Site Settings {siteNumber}]";
newSiteSettings.NotFoundPage = notFoundReference.ToReferenceWithoutVersion();
newSiteSettings.SiteName = $"Site {siteNumber}";

var siteSettingsReference = contentRepository.Save(newSiteSettings, SaveAction.Publish, AccessLevel.NoAccess);

// Update Home Page
var existingHomePage = contentRepository.Get<HomePage>(homePageReference, culture);
var editableHomePage = existingHomePage.CreateWritableClone() as HomePage;
editableHomePage.SiteSettings = siteSettingsReference.ToReferenceWithoutVersion();

homePageReference = contentRepository.Save(editableHomePage, SaveAction.Publish, AccessLevel.NoAccess);

// Create Site
var newSite = new InProcessWebsite($"TestWebsite{siteNumber}", homePageReference.ToReferenceWithoutVersion())
{
return;
}
DisplayName = $"Test Website {siteNumber}"
};

newSite.Hosts.Add(new ApplicationHost($"localhost:{primaryPort}")
{
PreferredUrlScheme = UrlScheme.Https,
Type = ApplicationHostType.Primary
});

var contentRepository = ServiceLocator.Current.GetInstance<IContentRepository>();
newSite.Hosts.Add(new ApplicationHost($"localhost:{cmsPort}")
{
PreferredUrlScheme = UrlScheme.Https,
Type = ApplicationHostType.Edit
});

appRepository.SaveAsync(newSite).GetAwaiter().GetResult();
}

private static void SetUpHeadlessSystem(IApplicationRepository appRepository, IContentRepository contentRepository, int siteNumber, int primaryPort, int previewPort)
{
var culture = new CultureInfo("en");

// Create HomePage
var newHomePage = contentRepository.GetDefault<HomePage>(ContentReference.RootPage, culture);
newHomePage.Name = "Home";
newHomePage.Heading = "Home";
newHomePage.MetaTitle = "Home";
newHomePage.Name = $"Home {siteNumber}";
newHomePage.Heading = $"Home {siteNumber}";
newHomePage.MetaTitle = $"Home {siteNumber}";

var homePageReference = contentRepository.Save(newHomePage, SaveAction.Publish, AccessLevel.NoAccess);

// Create Not Found Page
var newNotFoundPage = contentRepository.GetDefault<NotFoundPage>(ContentReference.RootPage, culture);
newNotFoundPage.Name = "Not Found";
newNotFoundPage.MetaTitle = "Home";
var newNotFoundPage = contentRepository.GetDefault<NotFoundPage>(homePageReference, culture);
newNotFoundPage.Name = $"Not Found {siteNumber}";
newNotFoundPage.MetaTitle = $"Not Found {siteNumber}";

var notFoundReference = contentRepository.Save(newNotFoundPage, SaveAction.Publish, AccessLevel.NoAccess);

// Create SiteSettings
var newSiteSettings = contentRepository.GetDefault<SiteSettingsPage>(homePageReference, culture);
newSiteSettings.Name = "[Site Settings]";
newSiteSettings.Name = $"[Site Settings {siteNumber}]";
newSiteSettings.NotFoundPage = notFoundReference.ToReferenceWithoutVersion();
newSiteSettings.SiteName = "Stott Security Dev Site";
newSiteSettings.SiteName = $"Site {siteNumber}";

var siteSettingsReference = contentRepository.Save(newSiteSettings, SaveAction.Publish, AccessLevel.NoAccess);

Expand All @@ -73,20 +133,24 @@ private void SetUpSystem()
homePageReference = contentRepository.Save(editableHomePage, SaveAction.Publish, AccessLevel.NoAccess);

// Create Site
var newSite = SiteDefinition.Empty;
var editableSite = newSite.CreateWritableClone();
var newSite = new Website($"TestWebsite{siteNumber}", homePageReference.ToReferenceWithoutVersion())
{
DisplayName = $"Test Website {siteNumber}"
};

newSite.Hosts.Add(new ApplicationHost($"localhost:{primaryPort}")
{
PreferredUrlScheme = UrlScheme.Https,
Type = ApplicationHostType.Primary
});

editableSite.Name = "Test Website";
editableSite.StartPage = homePageReference.ToReferenceWithoutVersion();
editableSite.SiteUrl = new Uri("https://localhost:44344/");
editableSite.Hosts.Add(new HostDefinition
newSite.Hosts.Add(new ApplicationHost($"localhost:{previewPort}")
{
Name = "localhost:44344",
Type = HostDefinitionType.Primary,
UseSecureConnection = true
PreferredUrlScheme = UrlScheme.Https,
Type = ApplicationHostType.Preview
});

siteRepository.Save(editableSite);
appRepository.SaveAsync(newSite).GetAwaiter().GetResult();
}
}
}
Loading
Loading