From c9254e83ffc0cbcc537a3aa20fd6c309ea564ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dra=C5=BEen=20Stankovi=C4=87?= Date: Fri, 8 May 2026 15:21:03 +0200 Subject: [PATCH 1/3] code ready for testing --- .claude/settings.local.json | 9 ++++++ .../Geta.Optimizely.Extensions.Web.csproj | 6 ++-- .../ContentAreaExtensions.cs | 2 +- .../Geta.Optimizely.Extensions.csproj | 8 ++--- .../JsonExtensions.cs | 29 +++++++------------ .../StringExtensions.cs | 14 +++++---- .../TypeExtensions.cs | 2 +- .../UrlHelperExtensions.cs | 5 ++-- sub/geta-foundation-core | 2 +- .../Geta.Optimizely.Extensions.Tests.csproj | 19 ++++++------ 10 files changed, 49 insertions(+), 47 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..c684b20 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,9 @@ +{ + "permissions": { + "allow": [ + "Bash(dotnet build *)", + "Bash(dotnet restore *)", + "Bash(dotnet test *)" + ] + } +} diff --git a/src/Geta.Optimizely.Extensions.Web/Geta.Optimizely.Extensions.Web.csproj b/src/Geta.Optimizely.Extensions.Web/Geta.Optimizely.Extensions.Web.csproj index d97afd3..b6b43b9 100644 --- a/src/Geta.Optimizely.Extensions.Web/Geta.Optimizely.Extensions.Web.csproj +++ b/src/Geta.Optimizely.Extensions.Web/Geta.Optimizely.Extensions.Web.csproj @@ -2,7 +2,7 @@ Exe - net9.0 + net10.0 enable enable true @@ -13,10 +13,10 @@ - + - + diff --git a/src/Geta.Optimizely.Extensions/ContentAreaExtensions.cs b/src/Geta.Optimizely.Extensions/ContentAreaExtensions.cs index 0fd42d7..9806208 100644 --- a/src/Geta.Optimizely.Extensions/ContentAreaExtensions.cs +++ b/src/Geta.Optimizely.Extensions/ContentAreaExtensions.cs @@ -15,7 +15,7 @@ public static class ContentAreaExtensions /// Returns true if content area has content and false when not. public static bool HasContent(this ContentArea contentArea) { - return contentArea?.FilteredItems != null && contentArea.FilteredItems.Any(); + return contentArea?.Items != null && contentArea.Items.Any(); } } } \ No newline at end of file diff --git a/src/Geta.Optimizely.Extensions/Geta.Optimizely.Extensions.csproj b/src/Geta.Optimizely.Extensions/Geta.Optimizely.Extensions.csproj index eec4c09..7a1c6d8 100644 --- a/src/Geta.Optimizely.Extensions/Geta.Optimizely.Extensions.csproj +++ b/src/Geta.Optimizely.Extensions/Geta.Optimizely.Extensions.csproj @@ -1,7 +1,7 @@  - net5.0 + net10.0 Geta.Optimizely.Extensions Extensions and helpers library for Optimizely CMS Geta Digital @@ -18,9 +18,9 @@ - - - + + + diff --git a/src/Geta.Optimizely.Extensions/JsonExtensions.cs b/src/Geta.Optimizely.Extensions/JsonExtensions.cs index 37ad732..1784092 100644 --- a/src/Geta.Optimizely.Extensions/JsonExtensions.cs +++ b/src/Geta.Optimizely.Extensions/JsonExtensions.cs @@ -1,31 +1,22 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using Newtonsoft.Json.Serialization; +using System.Text.Json; +using System.Text.Json.Serialization; namespace Geta.Optimizely.Extensions { - /// - /// Extension methods for working with JSON data - /// public static class JsonExtensions { - /// - /// Convert object to JSON - /// - /// - /// Object to convert - /// Include null property values - /// public static string ToJson(this T obj, bool includeNull = true) { - var settings = new JsonSerializerSettings + var options = new JsonSerializerOptions { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - Converters = new JsonConverter[] { new StringEnumConverter() }, - NullValueHandling = includeNull ? NullValueHandling.Include : NullValueHandling.Ignore + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = includeNull + ? JsonIgnoreCondition.Never + : JsonIgnoreCondition.WhenWritingNull, + Converters = { new JsonStringEnumConverter() } }; - return JsonConvert.SerializeObject(obj, settings); + return JsonSerializer.Serialize(obj, options); } } -} \ No newline at end of file +} diff --git a/src/Geta.Optimizely.Extensions/StringExtensions.cs b/src/Geta.Optimizely.Extensions/StringExtensions.cs index 8288f7d..485f0c0 100644 --- a/src/Geta.Optimizely.Extensions/StringExtensions.cs +++ b/src/Geta.Optimizely.Extensions/StringExtensions.cs @@ -1,6 +1,6 @@ -using EPiServer.Core.Html; -using Geta.Net.Extensions; +using Geta.Net.Extensions; using System; +using System.Text.RegularExpressions; using Microsoft.AspNetCore.Http; namespace Geta.Optimizely.Extensions @@ -18,9 +18,13 @@ public static class StringExtensions /// A string with text. public static string StripHtml(this string htmlText, int maxLength = 0) { - return string.IsNullOrWhiteSpace(htmlText) - ? htmlText - : TextIndexer.StripHtml(htmlText, maxLength); + if (string.IsNullOrWhiteSpace(htmlText)) + return htmlText; + + var stripped = Regex.Replace(htmlText, "<[^>]*>", string.Empty); + return maxLength > 0 && stripped.Length > maxLength + ? stripped[..maxLength] + : stripped; } /// diff --git a/src/Geta.Optimizely.Extensions/TypeExtensions.cs b/src/Geta.Optimizely.Extensions/TypeExtensions.cs index ae86abc..193445c 100644 --- a/src/Geta.Optimizely.Extensions/TypeExtensions.cs +++ b/src/Geta.Optimizely.Extensions/TypeExtensions.cs @@ -16,7 +16,7 @@ public static class TypeExtensions /// PageType instance if found. public static PageType GetPageType(this Type pageType) { - return ServiceLocator.Current.GetInstance>().Load(pageType); + return ServiceLocator.Current.GetInstance().Load(pageType) as PageType; } } } diff --git a/src/Geta.Optimizely.Extensions/UrlHelperExtensions.cs b/src/Geta.Optimizely.Extensions/UrlHelperExtensions.cs index 636de0d..11ee8d2 100644 --- a/src/Geta.Optimizely.Extensions/UrlHelperExtensions.cs +++ b/src/Geta.Optimizely.Extensions/UrlHelperExtensions.cs @@ -1,5 +1,4 @@ -using Castle.Core.Internal; -using EPiServer; +using EPiServer; using EPiServer.Core; using EPiServer.ServiceLocation; using EPiServer.Web.Mvc.Html; @@ -30,7 +29,7 @@ public static class UrlHelperExtensions public static IHtmlContent PageLinkUrl(this IUrlHelper urlHelper, PageReference pageLink, string defaultValue) { var url = urlHelper.PageLinkUrl(pageLink) as HtmlString; - return url == null || url.Value.IsNullOrEmpty() ? new HtmlString(defaultValue) : url; + return url == null || string.IsNullOrEmpty(url.Value) ? new HtmlString(defaultValue) : url; } /// diff --git a/sub/geta-foundation-core b/sub/geta-foundation-core index d71244c..40971e5 160000 --- a/sub/geta-foundation-core +++ b/sub/geta-foundation-core @@ -1 +1 @@ -Subproject commit d71244c7c12339a3dfb278ba15ca423768c33118 +Subproject commit 40971e5afec808e72bb3011d03d8d49125298f5c diff --git a/test/Geta.Optimizely.Extensions.Tests/Geta.Optimizely.Extensions.Tests.csproj b/test/Geta.Optimizely.Extensions.Tests/Geta.Optimizely.Extensions.Tests.csproj index 3d8d448..44658e2 100644 --- a/test/Geta.Optimizely.Extensions.Tests/Geta.Optimizely.Extensions.Tests.csproj +++ b/test/Geta.Optimizely.Extensions.Tests/Geta.Optimizely.Extensions.Tests.csproj @@ -1,26 +1,25 @@  - net6.0 + net10.0 false - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 2ef018735d74ce86c2887346cf1c533f321690b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dra=C5=BEen=20Stankovi=C4=87?= Date: Wed, 27 May 2026 12:52:25 +0200 Subject: [PATCH 2/3] updated connections --- .../appsettings.json | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/Geta.Optimizely.Extensions.Web/appsettings.json b/src/Geta.Optimizely.Extensions.Web/appsettings.json index 98f452c..500a941 100644 --- a/src/Geta.Optimizely.Extensions.Web/appsettings.json +++ b/src/Geta.Optimizely.Extensions.Web/appsettings.json @@ -11,13 +11,15 @@ } }, "AllowedHosts": "*", - "EPiServer": { - "Find": { - "DefaultIndex": "changeme", - "ServiceUrl": "http://changeme", - "TrackingSanitizerEnabled": true, - "TrackingTimeout": 30000 - }, + "Optimizely": { + "ContentGraph": { + "GatewayAddress": "https://cg.optimizely.com", + "AppKey": "changeme", + "Secret": "changeme", + "SingleKey": "changeme" + } + }, + "EPiServer": { "OdpVisitorGroupOptions": { "OdpCookieName": "vuid", "CacheTimeoutSeconds": 1, From 58c09a88a113928ba4015c29934aa51f3d64c9da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dra=C5=BEen=20Stankovi=C4=87?= Date: Wed, 27 May 2026 14:32:46 +0200 Subject: [PATCH 3/3] fix code reviews --- .../ContentAreaExtensions.cs | 43 ++++++++++++++++--- .../JsonExtensions.cs | 25 ++++++----- .../StringExtensions.cs | 9 ++-- 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/src/Geta.Optimizely.Extensions/ContentAreaExtensions.cs b/src/Geta.Optimizely.Extensions/ContentAreaExtensions.cs index 9806208..819c3be 100644 --- a/src/Geta.Optimizely.Extensions/ContentAreaExtensions.cs +++ b/src/Geta.Optimizely.Extensions/ContentAreaExtensions.cs @@ -1,5 +1,8 @@ -using System.Linq; +using System.Linq; +using EPiServer; using EPiServer.Core; +using EPiServer.Security; +using EPiServer.ServiceLocation; namespace Geta.Optimizely.Extensions { @@ -9,13 +12,43 @@ namespace Geta.Optimizely.Extensions public static class ContentAreaExtensions { /// - /// Checks if content area has content. + /// Checks if content area has any content that is visible to the current user + /// (published, not expired and not access restricted). /// /// The content area. - /// Returns true if content area has content and false when not. + /// Returns true if the content area has at least one visible item and false when not. public static bool HasContent(this ContentArea contentArea) { - return contentArea?.Items != null && contentArea.Items.Any(); + if (contentArea?.Items == null) + { + return false; + } + + var contentLoader = ServiceLocator.Current.GetInstance(); + var publishedStateAssessor = ServiceLocator.Current.GetInstance(); + var accessEvaluator = ServiceLocator.Current.GetInstance(); + var principal = ServiceLocator.Current.GetInstance().Principal; + + return contentArea.Items.Any(item => IsVisible(item, contentLoader, publishedStateAssessor, accessEvaluator, principal)); + } + + // CMS 13 removed ContentArea.FilteredItems. This replicates its visibility filtering + // (publish state, expiration and read access) so HasContent does not report items + // that would not actually be rendered for the current user. + private static bool IsVisible( + ContentAreaItem item, + IContentLoader contentLoader, + IPublishedStateAssessor publishedStateAssessor, + IContentAccessEvaluator accessEvaluator, + System.Security.Principal.IPrincipal principal) + { + if (item?.ContentLink == null || !contentLoader.TryGet(item.ContentLink, out var content)) + { + return false; + } + + return publishedStateAssessor.IsPublished(content) + && accessEvaluator.HasAccess(content, principal, AccessLevel.Read); } } -} \ No newline at end of file +} diff --git a/src/Geta.Optimizely.Extensions/JsonExtensions.cs b/src/Geta.Optimizely.Extensions/JsonExtensions.cs index 1784092..50866f6 100644 --- a/src/Geta.Optimizely.Extensions/JsonExtensions.cs +++ b/src/Geta.Optimizely.Extensions/JsonExtensions.cs @@ -5,18 +5,23 @@ namespace Geta.Optimizely.Extensions { public static class JsonExtensions { - public static string ToJson(this T obj, bool includeNull = true) + private static readonly JsonSerializerOptions OptionsWithNulls = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Converters = { new JsonStringEnumConverter() } + }; + + private static readonly JsonSerializerOptions OptionsWithoutNulls = new() { - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - DefaultIgnoreCondition = includeNull - ? JsonIgnoreCondition.Never - : JsonIgnoreCondition.WhenWritingNull, - Converters = { new JsonStringEnumConverter() } - }; + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = { new JsonStringEnumConverter() } + }; - return JsonSerializer.Serialize(obj, options); + public static string ToJson(this T obj, bool includeNull = true) + { + return JsonSerializer.Serialize(obj, includeNull ? OptionsWithNulls : OptionsWithoutNulls); } } } diff --git a/src/Geta.Optimizely.Extensions/StringExtensions.cs b/src/Geta.Optimizely.Extensions/StringExtensions.cs index 485f0c0..61c30f4 100644 --- a/src/Geta.Optimizely.Extensions/StringExtensions.cs +++ b/src/Geta.Optimizely.Extensions/StringExtensions.cs @@ -1,6 +1,7 @@ -using Geta.Net.Extensions; using System; +using System.Net; using System.Text.RegularExpressions; +using Geta.Net.Extensions; using Microsoft.AspNetCore.Http; namespace Geta.Optimizely.Extensions @@ -21,7 +22,9 @@ public static string StripHtml(this string htmlText, int maxLength = 0) if (string.IsNullOrWhiteSpace(htmlText)) return htmlText; - var stripped = Regex.Replace(htmlText, "<[^>]*>", string.Empty); + + var stripped = Regex.Replace(htmlText, "<[^>]*>", string.Empty, RegexOptions.None, TimeSpan.FromSeconds(1)); + stripped = WebUtility.HtmlDecode(stripped); return maxLength > 0 && stripped.Length > maxLength ? stripped[..maxLength] : stripped; @@ -104,4 +107,4 @@ public static string AddHost(this string url, Func getBaseUri) return uriBuilder.ToString(); } } -} \ No newline at end of file +}