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.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, diff --git a/src/Geta.Optimizely.Extensions/ContentAreaExtensions.cs b/src/Geta.Optimizely.Extensions/ContentAreaExtensions.cs index 0fd42d7..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?.FilteredItems != null && contentArea.FilteredItems.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/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..50866f6 100644 --- a/src/Geta.Optimizely.Extensions/JsonExtensions.cs +++ b/src/Geta.Optimizely.Extensions/JsonExtensions.cs @@ -1,31 +1,27 @@ -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) + private static readonly JsonSerializerOptions OptionsWithNulls = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + Converters = { new JsonStringEnumConverter() } + }; + + private static readonly JsonSerializerOptions OptionsWithoutNulls = new() { - var settings = new JsonSerializerSettings - { - ContractResolver = new CamelCasePropertyNamesContractResolver(), - Converters = new JsonConverter[] { new StringEnumConverter() }, - NullValueHandling = includeNull ? NullValueHandling.Include : NullValueHandling.Ignore - }; + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + Converters = { new JsonStringEnumConverter() } + }; - return JsonConvert.SerializeObject(obj, settings); + public static string ToJson(this T obj, bool includeNull = true) + { + return JsonSerializer.Serialize(obj, includeNull ? OptionsWithNulls : OptionsWithoutNulls); } } -} \ No newline at end of file +} diff --git a/src/Geta.Optimizely.Extensions/StringExtensions.cs b/src/Geta.Optimizely.Extensions/StringExtensions.cs index 8288f7d..61c30f4 100644 --- a/src/Geta.Optimizely.Extensions/StringExtensions.cs +++ b/src/Geta.Optimizely.Extensions/StringExtensions.cs @@ -1,6 +1,7 @@ -using EPiServer.Core.Html; -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 @@ -18,9 +19,15 @@ 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, RegexOptions.None, TimeSpan.FromSeconds(1)); + stripped = WebUtility.HtmlDecode(stripped); + return maxLength > 0 && stripped.Length > maxLength + ? stripped[..maxLength] + : stripped; } /// @@ -100,4 +107,4 @@ public static string AddHost(this string url, Func getBaseUri) return uriBuilder.ToString(); } } -} \ No newline at end of file +} 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