diff --git a/.gitignore b/.gitignore index c369668..39e5b27 100644 --- a/.gitignore +++ b/.gitignore @@ -197,10 +197,11 @@ ExamineIndexes/ */media/created-packages/ # Solution -TestWebApp.V9/ -TestWebApp.V10/ -TestWebApp.V11/ -TestWebApp.V12/ -TestWebApp.V13/ +TestWebApp.V14/ -deploy.ps1 \ No newline at end of file +PersonalisationGroups.Tests/*schema*.json + +deploy.ps1 + +PersonalisationGroups/wwwroot/*.js +PersonalisationGroups/wwwroot/*.map \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index dbd15ef..30b07aa 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,14 +1,14 @@  - 3.4.0 - 3.4.0 - 3.4.0 - 3.4.0 - 10.0 + 4.0.0-rc1 + 4.0.0 + 4.0.0-rc1 + 4.0.0 + 12.0 en-US - net5.0 + net8.0 Andy Butland - 2021-23 Andy Butland + 2021-24 Andy Butland https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/ https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/ Github diff --git a/PersonalisationGroups.Core/AppConfiguration.cs b/PersonalisationGroups.Core/AppConfiguration.cs deleted file mode 100644 index 0772b0a..0000000 --- a/PersonalisationGroups.Core/AppConfiguration.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Umbraco.Cms.Web.Common.ApplicationBuilder; - -namespace Our.Umbraco.PersonalisationGroups.Core -{ - public static class AppConfiguration - { - public static IUmbracoEndpointBuilderContext UsePersonalisationGroupsEndpoints(this IUmbracoEndpointBuilderContext builderContext) - { - builderContext.EndpointRouteBuilder.MapControllerRoute( - name: "Criteria methods", - pattern: "App_Plugins/PersonalisationGroups/Criteria", - defaults: new { controller = "Criteria", action = "Index" }); - builderContext.EndpointRouteBuilder.MapControllerRoute( - name: "Geo location methods", - pattern: "App_Plugins/PersonalisationGroups/GeoLocation/{action}", - defaults: new { controller = "GeoLocation", action = "Index" }); - builderContext.EndpointRouteBuilder.MapControllerRoute( - name: "Member methods", - pattern: "App_Plugins/PersonalisationGroups/Member/{action}", - defaults: new { controller = "Member", action = "Index" }); - - return builderContext; - } - } -} diff --git a/PersonalisationGroups.Core/AppConstants.cs b/PersonalisationGroups.Core/AppConstants.cs deleted file mode 100644 index 70fd76a..0000000 --- a/PersonalisationGroups.Core/AppConstants.cs +++ /dev/null @@ -1,71 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core -{ - public enum Comparison - { - GreaterThan, - GreaterThanOrEqual, - LessThan, - LessThanOrEqual, - } - - public static class AppConstants - { - public const string PackageName = "Personalisation Groups"; - - public const string DefaultGroupPickerAlias = "personalisationGroups"; - - public const string PersonalisationGroupDefinitionPropertyEditorAlias = "personalisationGroupDefinition"; - - public const string PersonalisationGroupDefinitionPropertyAlias = "definition"; - - public const string CommonAssemblyName = "Our.Umbraco.PersonalisationGroups"; - - public const string DefaultGeoLocationCountryDatabasePath = "/umbraco/Data/PersonalisationGroups/GeoLite2-Country.mmdb"; - - public const string DefaultGeoLocationCityDatabasePath = "/umbraco/Data/PersonalisationGroups/GeoLite2-City.mmdb"; - - public const int DefaultNumberOfVisitsTrackingCookieExpiryInDays = 90; - - public const int DefaultViewedPagesTrackingCookieExpiryInDays = 90; - - public const int DefaultPersistentMatchedGroupsCookieExpiryInDays = 90; - - public const string DefaultCookieKeyForTrackingNumberOfVisits = "personalisationGroupsNumberOfVisits"; - - public const string DefaultCookieKeyForTrackingIfSessionAlreadyTracked = "personalisationGroupsNumberOfVisitsSessionStarted"; - - public const string DefaultCookieKeyForTrackingPagesViewed = "personalisationGroupsPagesViewed"; - - public const string DefaultCookieKeyForSessionMatchedGroups = "sessionMatchedGroups"; - - public const string DefaultCookieKeyForPersistentMatchedGroups = "persistentMatchedGroups"; - - public const string DefaultCookieKeyForTrackingCookiesDeclined = "personalisationGroupsCookiesDeclined"; - - public const string DefaultSessionKeyForTrackingCookiesDeclined = "PersonalisationGroups_CookiesDeclined"; - - public const string DefaultCdnCountryCodeHttpHeaderName = "CF-IPCountry"; - - public static class DocumentTypeAliases - { - public const string PersonalisationGroupsFolder = "PersonalisationGroupsFolder"; - - public const string PersonalisationGroup = "PersonalisationGroup"; - } - - public static class CacheKeys - { - public const string PersonalisationGroupsVisitorHash = "PersonalisationGroups.VisitorHash"; - } - - public static class SessionKeys - { - public const string PersonalisationGroupsEnsureSession = "PersonalisationGroups.EnsureSession"; - } - - public static class System - { - public const string MigrationPlanName = "PersonalisationGroups"; - } - } -} diff --git a/PersonalisationGroups.Core/Attributes/CriteriaResourceAssemblyAttribute.cs b/PersonalisationGroups.Core/Attributes/CriteriaResourceAssemblyAttribute.cs deleted file mode 100644 index 67e95b4..0000000 --- a/PersonalisationGroups.Core/Attributes/CriteriaResourceAssemblyAttribute.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Attributes -{ - using System; - - [AttributeUsage(AttributeTargets.Class)] - public class CriteriaResourceAssemblyAttribute : Attribute - { - public string AssemblyName { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Configuration/PersonalisationGroupsConfig.cs b/PersonalisationGroups.Core/Configuration/PersonalisationGroupsConfig.cs deleted file mode 100644 index c520440..0000000 --- a/PersonalisationGroups.Core/Configuration/PersonalisationGroupsConfig.cs +++ /dev/null @@ -1,51 +0,0 @@ -using Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation; - -namespace Our.Umbraco.PersonalisationGroups.Core.Configuration -{ - public class PersonalisationGroupsConfig - { - public bool DisablePackage { get; set; } = false; - - public string GroupPickerAlias { get; set; } = AppConstants.DefaultGroupPickerAlias; - - public string GeoLocationCountryDatabasePath { get; set; } = AppConstants.DefaultGeoLocationCountryDatabasePath; - - public string GeoLocationCityDatabasePath { get; set; } = AppConstants.DefaultGeoLocationCityDatabasePath; - - public string GeoLocationRegionListPath { get; set; } = string.Empty; - - public string IncludeCriteria { get; set; } = string.Empty; - - public string ExcludeCriteria { get; set; } = string.Empty; - - public int NumberOfVisitsTrackingCookieExpiryInDays { get; set; } = AppConstants.DefaultNumberOfVisitsTrackingCookieExpiryInDays; - - public int ViewedPagesTrackingCookieExpiryInDays { get; set; } = AppConstants.DefaultViewedPagesTrackingCookieExpiryInDays; - - public string CookieKeyForTrackingNumberOfVisits { get; set; } = AppConstants.DefaultCookieKeyForTrackingNumberOfVisits; - - public string CookieKeyForTrackingIfSessionAlreadyTracked { get; set; } = AppConstants.DefaultCookieKeyForTrackingIfSessionAlreadyTracked; - - public string CookieKeyForTrackingPagesViewed { get; set; } = AppConstants.DefaultCookieKeyForTrackingPagesViewed; - - public string CookieKeyForSessionMatchedGroups { get; set; } = AppConstants.DefaultCookieKeyForSessionMatchedGroups; - - public string CookieKeyForPersistentMatchedGroups { get; set; } = AppConstants.DefaultCookieKeyForPersistentMatchedGroups; - - public string CookieKeyForTrackingCookiesDeclined { get; set; } = AppConstants.DefaultCookieKeyForTrackingCookiesDeclined; - - public string SessionKeyForTrackingCookiesDeclined { get; set; } = AppConstants.DefaultSessionKeyForTrackingCookiesDeclined; - - public int PersistentMatchedGroupsCookieExpiryInDays { get; set; } = AppConstants.DefaultPersistentMatchedGroupsCookieExpiryInDays; - - public string TestFixedIp { get; set; } = string.Empty; - - public CountryCodeProvider CountryCodeProvider { get; set; } = CountryCodeProvider.MaxMindDatabase; - - public string CdnCountryCodeHttpHeaderName { get; set; } = AppConstants.DefaultCdnCountryCodeHttpHeaderName; - - public bool DisableHttpContextItemsUseInCookieOperations { get; set; } = false; - - public bool DisableUserActivityTracking { get; set; } = false; - } -} diff --git a/PersonalisationGroups.Core/Controllers/CriteriaController.cs b/PersonalisationGroups.Core/Controllers/CriteriaController.cs deleted file mode 100644 index 080e958..0000000 --- a/PersonalisationGroups.Core/Controllers/CriteriaController.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Our.Umbraco.PersonalisationGroups.Core.Services; - -namespace Our.Umbraco.PersonalisationGroups.Core.Controllers -{ - /// - /// Controller making available criteria details to HTTP requests - /// - public class CriteriaController : ControllerBase - { - private readonly ICriteriaService _criteriaService; - - public CriteriaController(ICriteriaService criteriaService) - { - _criteriaService = criteriaService; - } - - /// - /// Gets a list of the available criteria - /// - /// JSON response of available criteria - /// Using ContentResult so can serialize with camel case for consistency in client-side code - [HttpGet] - public IActionResult Index() - { - var criteria = _criteriaService.GetAvailableCriteria(); - return new OkObjectResult(criteria); - } - } -} diff --git a/PersonalisationGroups.Core/Controllers/GeoLocationController.cs b/PersonalisationGroups.Core/Controllers/GeoLocationController.cs deleted file mode 100644 index 197b2db..0000000 --- a/PersonalisationGroups.Core/Controllers/GeoLocationController.cs +++ /dev/null @@ -1,212 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Extensions; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Hosting; - -namespace Our.Umbraco.PersonalisationGroups.Core.Controllers -{ - /// - /// Controller making available country & region details to HTTP requests - /// - public class GeoLocationController : ControllerBase - { - private readonly PersonalisationGroupsConfig _config; - private readonly IHostingEnvironment _hostingEnvironment; - private readonly AppCaches _appCaches; - - public GeoLocationController(IOptions config, IHostingEnvironment hostingEnvironment, AppCaches appCaches) - { - _config = config.Value; - _hostingEnvironment = hostingEnvironment; - _appCaches = appCaches; - } - - /// - /// Gets a list of the available continents - /// - /// JSON response of available criteria - [HttpGet] - public IActionResult GetContinents() - { - var cacheKey = $"PersonalisationGroups_GeoLocation_Continents"; - var countries = _appCaches.RuntimeCache.Get(cacheKey, - () => - { - var assembly = GetResourceAssembly(); - var resourceName = GetResourceName("continents"); - using (var stream = assembly.GetManifestResourceStream(resourceName)) - { - if (stream == null) - { - return null; - } - - using (var reader = new StreamReader(stream)) - { - var continentRecords = reader.ReadToEnd() - .SplitByNewLine(StringSplitOptions.RemoveEmptyEntries) - .Select(x => new - { - code = x.Split(',')[0], - name = CleanName(x.Split(',')[1]) - }); - - continentRecords = continentRecords.OrderBy(x => x.name); - - return continentRecords; - } - } - }); - - return new OkObjectResult(countries); - } - - /// - /// Gets a list of the available countries - /// - /// JSON response of available criteria - [HttpGet] - public IActionResult GetCountries(bool withRegionsOnly = false) - { - var cacheKey = $"PersonalisationGroups_GeoLocation_Countries_{withRegionsOnly}"; - var countries = _appCaches.RuntimeCache.Get(cacheKey, - () => - { - var assembly = GetResourceAssembly(); - var resourceName = GetResourceName("countries"); - using (var stream = assembly.GetManifestResourceStream(resourceName)) - { - if (stream == null) - { - return null; - } - - using (var reader = new StreamReader(stream)) - { - var countryRecords = reader.ReadToEnd() - .SplitByNewLine(StringSplitOptions.RemoveEmptyEntries) - .Select(x => new - { - code = x.Split(',')[0], - name = CleanName(x.Split(',')[1]) - }); - - if (withRegionsOnly) - { - var countryCodesWithRegions = GetCountryCodesWithRegions(assembly); - countryRecords = countryRecords - .Where(x => countryCodesWithRegions.Contains(x.code)); - } - - countryRecords = countryRecords.OrderBy(x => x.name); - - return countryRecords; - } - } - }); - - return new OkObjectResult(countries); - } - - /// - /// Gets a list of the available regions for a given country - /// - /// Country code - /// JSON response of available criteria - [HttpGet] - public IActionResult GetRegions(string countryCode) - { - var cacheKey = $"PersonalisationGroups_GeoLocation_Regions_{countryCode}"; - var regions = _appCaches.RuntimeCache.Get(cacheKey, - () => - { - using (var stream = GetStreamForRegions()) - { - if (stream == null) - { - return null; - } - - using (var reader = new StreamReader(stream)) - { - var streamContents = reader.ReadToEnd(); - var regionRecords = streamContents - .SplitByNewLine(StringSplitOptions.RemoveEmptyEntries) - .Where(x => x.Split(',')[0] == countryCode.ToUpperInvariant()) - .Select(x => new - { - code = x.Split(',')[1], - name = CleanName(x.Split(',')[2]) - }) - .OrderBy(x => x.name); - return regionRecords; - } - } - }); - - return new OkObjectResult(regions); - } - - private static Assembly GetResourceAssembly() - { - return Assembly.Load(AppConstants.CommonAssemblyName); - } - - private static string GetResourceName(string area) - { - return $"{AppConstants.CommonAssemblyName}.Data.{area}.txt"; - } - - private static string CleanName(string name) - { - return name.Replace("\"", string.Empty).Trim(); - } - - private static IEnumerable GetCountryCodesWithRegions(Assembly assembly) - { - var resourceName = GetResourceName("regions"); - using (var stream = assembly.GetManifestResourceStream(resourceName)) - { - if (stream == null) - { - return new string[0]; - } - - using (var reader = new StreamReader(stream)) - { - return reader.ReadToEnd() - .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => x.Split(',')[0]) - .Distinct() - .ToArray(); - } - } - } - - private Stream GetStreamForRegions() - { - // First try to use custom file path if provided in configuration. - var customFilePath = _config.GeoLocationRegionListPath; - if (!string.IsNullOrEmpty(customFilePath)) - { - var mappedPath = _hostingEnvironment.MapPathContentRoot(customFilePath); - if (!string.IsNullOrEmpty(mappedPath) && System.IO.File.Exists(mappedPath)) - { - return System.IO.File.OpenRead(mappedPath); - } - } - - // Otherwise fall back to provided resource file. - var assembly = GetResourceAssembly(); - var resourceName = GetResourceName("regions"); - return assembly.GetManifestResourceStream(resourceName); - } - } -} diff --git a/PersonalisationGroups.Core/Controllers/MemberController.cs b/PersonalisationGroups.Core/Controllers/MemberController.cs deleted file mode 100644 index 1393b3a..0000000 --- a/PersonalisationGroups.Core/Controllers/MemberController.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Microsoft.AspNetCore.Mvc; -using System.Linq; -using Umbraco.Cms.Core.Services; - -namespace Our.Umbraco.PersonalisationGroups.Core.Controllers -{ - /// - /// Controller making available member details to HTTP requests - /// - public class MemberController : ControllerBase - { - private readonly IMemberTypeService _memberTypeService; - private readonly IMemberService _memberService; - - public MemberController(IMemberTypeService memberTypeService, IMemberService memberService) - { - _memberTypeService = memberTypeService; - _memberService = memberService; - } - - /// - /// Gets a list of the available member types - /// - /// JSON response of available criteria - [HttpGet] - public IActionResult GetMemberTypes() - { - var memberTypes = _memberTypeService.GetAll() - .OrderBy(x => x.Alias) - .Select(x => x.Alias); - return new OkObjectResult(memberTypes); - } - - /// - /// Gets a list of the available member groups - /// - /// JSON response of available criteria - [HttpGet] - public IActionResult GetMemberGroups() - { - var memberGroups = _memberService.GetAllRoles() - .Select(x => x.Name) - .OrderBy(x => x); - return new OkObjectResult(memberGroups); - } - - /// - /// Gets a list of the available member profile fields - /// - /// JSON response of available criteria - [HttpGet] - public IActionResult GetMemberProfileFields() - { - var memberTypes = _memberTypeService.GetAll(); - var fields = memberTypes - .SelectMany(x => x.PropertyTypes) - .Select(x => x.Alias) - .Distinct() - .OrderBy(x => x); - - return new OkObjectResult(fields); - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/AuthenticationStatus/AuthenticationStatusPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/AuthenticationStatus/AuthenticationStatusPersonalisationGroupCriteria.cs deleted file mode 100644 index 5f54690..0000000 --- a/PersonalisationGroups.Core/Criteria/AuthenticationStatus/AuthenticationStatusPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.AuthenticationStatus; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.AuthenticationStatus -{ - /// - /// Implements a personalisation group criteria based on whether the user is logged on or not - /// - public class AuthenticationStatusPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IAuthenticationStatusProvider _authenticationStatusProvider; - - public AuthenticationStatusPersonalisationGroupCriteria(IAuthenticationStatusProvider authenticationStatusProvider) - { - _authenticationStatusProvider = authenticationStatusProvider; - } - - public string Name => "Authentication status"; - - public string Alias => "authenticationStatus"; - - public string Description => "Matches visitor session with their authentication status"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - AuthenticationStatusSetting authenticationStatusSetting; - try - { - authenticationStatusSetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - return (authenticationStatusSetting.IsAuthenticated && _authenticationStatusProvider.IsAuthenticated()) || - (!authenticationStatusSetting.IsAuthenticated && !_authenticationStatusProvider.IsAuthenticated()); - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/AuthenticationStatus/AuthenticationStatusSetting.cs b/PersonalisationGroups.Core/Criteria/AuthenticationStatus/AuthenticationStatusSetting.cs deleted file mode 100644 index 0187607..0000000 --- a/PersonalisationGroups.Core/Criteria/AuthenticationStatus/AuthenticationStatusSetting.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.AuthenticationStatus -{ - public class AuthenticationStatusSetting - { - public bool IsAuthenticated { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Continent/ContinentPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/Continent/ContinentPersonalisationGroupCriteria.cs deleted file mode 100644 index feb9cc0..0000000 --- a/PersonalisationGroups.Core/Criteria/Continent/ContinentPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Linq; -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Ip; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Continent -{ - /// - /// Implements a personalisation group criteria based on the country derived from the vistor's IP address - /// - public class ContinentPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IIpProvider _ipProvider; - private readonly IGeoLocationProvider _geoLocationProvider; - - public ContinentPersonalisationGroupCriteria(IIpProvider ipProvider, IGeoLocationProvider geoLocationProvider) - { - _ipProvider = ipProvider; - _geoLocationProvider = geoLocationProvider; - } - - public string Name => "Continent"; - - public string Alias => "continent"; - - public string Description => "Matches visitor continent derived from their IP address to a given list of countries"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - ContinentSetting countrySetting; - try - { - countrySetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var ip = _ipProvider.GetIp(); - if (!string.IsNullOrEmpty(ip)) - { - var country = _geoLocationProvider.GetContinentFromIp(ip); - if (country != null) - { - if (countrySetting.Match == GeoLocationSettingMatch.CouldNotBeLocated) - { - // We can't locate, so return false. - return false; - } - - var matchedContinent = countrySetting.Codes - .Any(x => string.Equals(x, country.Code, StringComparison.InvariantCultureIgnoreCase)); - switch (countrySetting.Match) - { - case GeoLocationSettingMatch.IsLocatedIn: - return matchedContinent; - case GeoLocationSettingMatch.IsNotLocatedIn: - return !matchedContinent; - default: - return false; - } - } - } - - return countrySetting.Match == GeoLocationSettingMatch.CouldNotBeLocated; - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Continent/ContinentSetting.cs b/PersonalisationGroups.Core/Criteria/Continent/ContinentSetting.cs deleted file mode 100644 index d74f6b1..0000000 --- a/PersonalisationGroups.Core/Criteria/Continent/ContinentSetting.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Continent -{ - public class ContinentSetting - { - public GeoLocationSettingMatch Match { get; set; } - - public List Codes { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Cookie/CookiePersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/Cookie/CookiePersonalisationGroupCriteria.cs deleted file mode 100644 index 934cf79..0000000 --- a/PersonalisationGroups.Core/Criteria/Cookie/CookiePersonalisationGroupCriteria.cs +++ /dev/null @@ -1,96 +0,0 @@ -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Cookie; -using System; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Cookie -{ - /// - /// Implements a personalisation group criteria based on the presence, absence or value of a cookie - /// - public class CookiePersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly ICookieProvider _cookieProvider; - - public CookiePersonalisationGroupCriteria(ICookieProvider cookieProvider) - { - _cookieProvider = cookieProvider; - } - - public string Name => "Cookie"; - - public string Alias => "cookie"; - - public string Description => "Matches visitor session with the presence, absence or value of a cookie"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - CookieSetting cookieSetting; - try - { - cookieSetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - if (string.IsNullOrEmpty(cookieSetting.Key)) - { - throw new ArgumentNullException("key", "Cookie key not set"); - } - - var cookieExists = _cookieProvider.CookieExists(cookieSetting.Key); - var cookieValue = string.Empty; - if (cookieExists) - { - cookieValue = _cookieProvider.GetCookieValue(cookieSetting.Key); - } - - switch (cookieSetting.Match) - { - case CookieSettingMatch.Exists: - return cookieExists; - case CookieSettingMatch.DoesNotExist: - return !cookieExists; - case CookieSettingMatch.MatchesValue: - return cookieExists && MatchesValue(cookieValue, cookieSetting.Value); - case CookieSettingMatch.ContainsValue: - return cookieExists && ContainsValue(cookieValue, cookieSetting.Value); - case CookieSettingMatch.GreaterThanValue: - case CookieSettingMatch.GreaterThanOrEqualToValue: - case CookieSettingMatch.LessThanValue: - case CookieSettingMatch.LessThanOrEqualToValue: - return cookieExists && - CompareValues(cookieValue, cookieSetting.Value, GetComparison(cookieSetting.Match)); - case CookieSettingMatch.MatchesRegex: - return cookieExists && MatchesRegex(cookieValue, cookieSetting.Value); - case CookieSettingMatch.DoesNotMatchRegex: - return !cookieExists || !MatchesRegex(cookieValue, cookieSetting.Value); - default: - return false; - } - } - - private static Comparison GetComparison(CookieSettingMatch settingMatch) - { - switch (settingMatch) - { - case CookieSettingMatch.GreaterThanValue: - return Comparison.GreaterThan; - case CookieSettingMatch.GreaterThanOrEqualToValue: - return Comparison.GreaterThanOrEqual; - case CookieSettingMatch.LessThanValue: - return Comparison.LessThan; - case CookieSettingMatch.LessThanOrEqualToValue: - return Comparison.LessThanOrEqual; - default: - throw new ArgumentException("Setting supplied does not match a comparison type", nameof(settingMatch)); - } - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Cookie/CookieSetting.cs b/PersonalisationGroups.Core/Criteria/Cookie/CookieSetting.cs deleted file mode 100644 index 7bc9f93..0000000 --- a/PersonalisationGroups.Core/Criteria/Cookie/CookieSetting.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Cookie -{ - public enum CookieSettingMatch - { - Exists, - DoesNotExist, - MatchesValue, - ContainsValue, - GreaterThanValue, - GreaterThanOrEqualToValue, - LessThanValue, - LessThanOrEqualToValue, - MatchesRegex, - DoesNotMatchRegex, - } - - public class CookieSetting - { - public string Key { get; set; } - - public CookieSettingMatch Match { get; set; } - - public string Value { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Country/CountryPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/Country/CountryPersonalisationGroupCriteria.cs deleted file mode 100644 index 7b57c4b..0000000 --- a/PersonalisationGroups.Core/Criteria/Country/CountryPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,68 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Country -{ - using System; - using System.Linq; - using Newtonsoft.Json; - using Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation; - - /// - /// Implements a personalisation group criteria based on the country derived from the vistor's IP address - /// - public class CountryPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly ICountryCodeProvider _countryCodeProvider; - - public CountryPersonalisationGroupCriteria(ICountryCodeProvider countryCodeProvider) - { - _countryCodeProvider = countryCodeProvider; - } - - public string Name => "Country"; - - public string Alias => "country"; - - public string Description => "Matches visitor country derived from their IP address to a given list of countries"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - CountrySetting countrySetting; - try - { - countrySetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var countryCode = _countryCodeProvider.GetCountryCode(); - if (!string.IsNullOrEmpty(countryCode)) - { - if (countrySetting.Match == GeoLocationSettingMatch.CouldNotBeLocated) - { - // We can't locate, so return false. - return false; - } - - var matchedCountry = countrySetting.Codes - .Any(x => string.Equals(x, countryCode, StringComparison.InvariantCultureIgnoreCase)); - switch (countrySetting.Match) - { - case GeoLocationSettingMatch.IsLocatedIn: - return matchedCountry; - case GeoLocationSettingMatch.IsNotLocatedIn: - return !matchedCountry; - default: - return false; - } - } - - return countrySetting.Match == GeoLocationSettingMatch.CouldNotBeLocated; - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Country/CountrySetting.cs b/PersonalisationGroups.Core/Criteria/Country/CountrySetting.cs deleted file mode 100644 index 848a878..0000000 --- a/PersonalisationGroups.Core/Criteria/Country/CountrySetting.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Country -{ - using System.Collections.Generic; - - public class CountrySetting - { - public GeoLocationSettingMatch Match { get; set; } - - public List Codes { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/DayOfWeek/DayOfWeekPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/DayOfWeek/DayOfWeekPersonalisationGroupCriteria.cs deleted file mode 100644 index bf3e901..0000000 --- a/PersonalisationGroups.Core/Criteria/DayOfWeek/DayOfWeekPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,44 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.DayOfWeek -{ - using Newtonsoft.Json; - using Our.Umbraco.PersonalisationGroups.Core.Providers.DateTime; - using System; - using System.Linq; - - /// - /// Implements a personalisation group criteria based on the day of the week - /// - public class DayOfWeekPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IDateTimeProvider _dateTimeProvider; - - public DayOfWeekPersonalisationGroupCriteria(IDateTimeProvider dateTimeProvider) - { - _dateTimeProvider = dateTimeProvider; - } - - public string Name => "Day of week"; - - public string Alias => "dayOfWeek"; - - public string Description => "Matches visitor session with defined days of the week"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - try - { - var definedDays = JsonConvert.DeserializeObject(definition); - return definedDays.Contains((int)_dateTimeProvider.GetCurrentDateTime().DayOfWeek + 1); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/GeoLocationSettingMatch.cs b/PersonalisationGroups.Core/Criteria/GeoLocationSettingMatch.cs deleted file mode 100644 index 7eda56d..0000000 --- a/PersonalisationGroups.Core/Criteria/GeoLocationSettingMatch.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria -{ - public enum GeoLocationSettingMatch - { - IsLocatedIn, - IsNotLocatedIn, - CouldNotBeLocated - } -} diff --git a/PersonalisationGroups.Core/Criteria/Host/HostPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/Host/HostPersonalisationGroupCriteria.cs deleted file mode 100644 index f697b26..0000000 --- a/PersonalisationGroups.Core/Criteria/Host/HostPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Host; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Host -{ - /// - /// Implements a personalisation group criteria based on the value of the host of the request URL - /// - public class HostPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IHostProvider _hostProvider; - - public HostPersonalisationGroupCriteria(IHostProvider hostProvider) - { - _hostProvider = hostProvider; - } - - public string Name => "Host"; - - public string Alias => "host"; - - public string Description => "Matches visitor with the URL host"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - HostSetting hostSetting; - try - { - hostSetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var host = _hostProvider.GetHost(); - switch (hostSetting.Match) - { - case HostSettingMatch.MatchesValue: - return MatchesValue(host, hostSetting.Value); - case HostSettingMatch.DoesNotMatchValue: - return !MatchesValue(host, hostSetting.Value); - case HostSettingMatch.ContainsValue: - return ContainsValue(host, hostSetting.Value); - case HostSettingMatch.DoesNotContainValue: - return !ContainsValue(host, hostSetting.Value); - default: - return false; - } - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Host/HostSetting.cs b/PersonalisationGroups.Core/Criteria/Host/HostSetting.cs deleted file mode 100644 index 160ef09..0000000 --- a/PersonalisationGroups.Core/Criteria/Host/HostSetting.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Host -{ - public enum HostSettingMatch - { - MatchesValue, - DoesNotMatchValue, - ContainsValue, - DoesNotContainValue, - } - - public class HostSetting - { - public HostSettingMatch Match { get; set; } - - public string Value { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/IPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/IPersonalisationGroupCriteria.cs deleted file mode 100644 index e221871..0000000 --- a/PersonalisationGroups.Core/Criteria/IPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,35 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria -{ - /// - /// Specifies an interface for a personalisation group criteria - /// - public interface IPersonalisationGroupCriteria - { - /// - /// Gets the name of the criteria - /// - string Name { get; } - - /// - /// Gets the unique alias of the criteria - /// - string Alias { get; } - - /// - /// Gets the description of the criteria - /// - string Description { get; } - - /// - /// Gets the client assets folder - /// - string ClientAssetsFolder { get; } - - /// - /// Checks whether the attributes of the current site visitor match the provided definition - /// - /// Definition of the criteria to check - /// True if definition matches visitor - bool MatchesVisitor(string definition); - } -} diff --git a/PersonalisationGroups.Core/Criteria/MemberGroup/MemberGroupPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/MemberGroup/MemberGroupPersonalisationGroupCriteria.cs deleted file mode 100644 index 0898ef2..0000000 --- a/PersonalisationGroups.Core/Criteria/MemberGroup/MemberGroupPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.MemberGroup; -using System; -using System.Linq; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.MemberGroup -{ - /// - /// Implements a personalisation group criteria based on the presence, absence or value of a session key - /// - public class MemberGroupPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IMemberGroupProvider _memberGroupProvider; - - public MemberGroupPersonalisationGroupCriteria(IMemberGroupProvider memberGroupProvider) - { - _memberGroupProvider = memberGroupProvider; - } - - public string Name => "Member group"; - - public string Alias => "memberGroup"; - - public string Description => "Matches authenticated visitor session with their member group"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - MemberGroupSetting setting; - try - { - setting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var memberGroups = _memberGroupProvider.GetMemberGroups(); - return (setting.Match == MemberGroupSettingMatch.IsInGroup && memberGroups.Contains(setting.GroupName, StringComparer.InvariantCultureIgnoreCase) || - (setting.Match == MemberGroupSettingMatch.IsNotInGroup && !memberGroups.Contains(setting.GroupName, StringComparer.InvariantCultureIgnoreCase))); - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/MemberGroup/MemberGroupSetting.cs b/PersonalisationGroups.Core/Criteria/MemberGroup/MemberGroupSetting.cs deleted file mode 100644 index ac51a7e..0000000 --- a/PersonalisationGroups.Core/Criteria/MemberGroup/MemberGroupSetting.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.MemberGroup -{ - public enum MemberGroupSettingMatch - { - IsInGroup, - IsNotInGroup, - } - - public class MemberGroupSetting - { - public MemberGroupSettingMatch Match { get; set; } - - public string GroupName { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/MemberProfileField/MemberProfileFieldPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/MemberProfileField/MemberProfileFieldPersonalisationGroupCriteria.cs deleted file mode 100644 index c8c8fae..0000000 --- a/PersonalisationGroups.Core/Criteria/MemberProfileField/MemberProfileFieldPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,77 +0,0 @@ -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.MemberProfileField; -using System; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.MemberProfileField -{ - /// - /// Implements a personalisation group criteria based on the presence, absence or value of a session key - /// - public class MemberProfileFieldPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IMemberProfileFieldProvider _memberProfileFieldProvider; - - public MemberProfileFieldPersonalisationGroupCriteria(IMemberProfileFieldProvider memberProfileFieldProvider) - { - _memberProfileFieldProvider = memberProfileFieldProvider; - } - - public string Name => "Member profile field"; - - public string Alias => "memberProfileField"; - - public string Description => "Matches authenticated visitor session with a field on their member profile"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - MemberProfileFieldSetting setting; - try - { - setting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var value = _memberProfileFieldProvider.GetMemberProfileFieldValue(setting.Alias); - - switch (setting.Match) - { - case MemberProfileFieldSettingMatch.MatchesValue: - return MatchesValue(value, setting.Value); - case MemberProfileFieldSettingMatch.DoesNotMatchValue: - return !MatchesValue(value, setting.Value); - case MemberProfileFieldSettingMatch.GreaterThanValue: - case MemberProfileFieldSettingMatch.GreaterThanOrEqualToValue: - case MemberProfileFieldSettingMatch.LessThanValue: - case MemberProfileFieldSettingMatch.LessThanOrEqualToValue: - return CompareValues(value, setting.Value, GetComparison(setting.Match)); - default: - return false; - } - } - - private static Comparison GetComparison(MemberProfileFieldSettingMatch settingMatch) - { - switch (settingMatch) - { - case MemberProfileFieldSettingMatch.GreaterThanValue: - return Comparison.GreaterThan; - case MemberProfileFieldSettingMatch.GreaterThanOrEqualToValue: - return Comparison.GreaterThanOrEqual; - case MemberProfileFieldSettingMatch.LessThanValue: - return Comparison.LessThan; - case MemberProfileFieldSettingMatch.LessThanOrEqualToValue: - return Comparison.LessThanOrEqual; - default: - throw new ArgumentException("Setting supplied does not match a comparison type", nameof(settingMatch)); - } - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/MemberProfileField/MemberProfileFieldSetting.cs b/PersonalisationGroups.Core/Criteria/MemberProfileField/MemberProfileFieldSetting.cs deleted file mode 100644 index 2562370..0000000 --- a/PersonalisationGroups.Core/Criteria/MemberProfileField/MemberProfileFieldSetting.cs +++ /dev/null @@ -1,21 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.MemberProfileField -{ - public enum MemberProfileFieldSettingMatch - { - MatchesValue, - DoesNotMatchValue, - GreaterThanValue, - GreaterThanOrEqualToValue, - LessThanValue, - LessThanOrEqualToValue, - } - - public class MemberProfileFieldSetting - { - public string Alias { get; set; } - - public MemberProfileFieldSettingMatch Match { get; set; } - - public string Value { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/MemberType/MemberTypePersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/MemberType/MemberTypePersonalisationGroupCriteria.cs deleted file mode 100644 index 2d65a48..0000000 --- a/PersonalisationGroups.Core/Criteria/MemberType/MemberTypePersonalisationGroupCriteria.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.MemberType; -using System; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.MemberType -{ - /// - /// Implements a personalisation group criteria based on the presence, absence or value of a session key - /// - public class MemberTypePersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IMemberTypeProvider _memberTypeProvider; - - public MemberTypePersonalisationGroupCriteria(IMemberTypeProvider memberTypeProvider) - { - _memberTypeProvider = memberTypeProvider; - } - - public string Name => "Member type"; - - public string Alias => "memberType"; - - public string Description => "Matches authenticated visitor session with their member type"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - MemberTypeSetting setting; - try - { - setting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var memberType = _memberTypeProvider.GetMemberType(); - return (setting.Match == MemberTypeSettingMatch.IsOfType && string.Equals(setting.TypeName, memberType, StringComparison.InvariantCultureIgnoreCase)) || - (setting.Match == MemberTypeSettingMatch.IsNotOfType && !string.Equals(setting.TypeName, memberType, StringComparison.InvariantCultureIgnoreCase)); - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/MemberType/MemberTypeSetting.cs b/PersonalisationGroups.Core/Criteria/MemberType/MemberTypeSetting.cs deleted file mode 100644 index 33bc098..0000000 --- a/PersonalisationGroups.Core/Criteria/MemberType/MemberTypeSetting.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.MemberType -{ - public enum MemberTypeSettingMatch - { - IsOfType, - IsNotOfType, - } - - public class MemberTypeSetting - { - public MemberTypeSettingMatch Match { get; set; } - - public string TypeName { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/MonthOfYear/MonthOfYearPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/MonthOfYear/MonthOfYearPersonalisationGroupCriteria.cs deleted file mode 100644 index 0e9a139..0000000 --- a/PersonalisationGroups.Core/Criteria/MonthOfYear/MonthOfYearPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.DateTime; -using System; -using System.Linq; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.MonthOfYear -{ - /// - /// Implements a personalisation group criteria based on the month of the year - /// - public class MonthOfYearPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IDateTimeProvider _dateTimeProvider; - - public MonthOfYearPersonalisationGroupCriteria(IDateTimeProvider dateTimeProvider) - { - _dateTimeProvider = dateTimeProvider; - } - - public string Name => "Month of year"; - - public string Alias => "monthOfYear"; - - public string Description => "Matches visitor session with defined months of the year"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - try - { - var definedMonths = JsonConvert.DeserializeObject(definition); - return definedMonths.Contains(_dateTimeProvider.GetCurrentDateTime().Month); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/NumberOfVisits/NumberOfVisitsPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/NumberOfVisits/NumberOfVisitsPersonalisationGroupCriteria.cs deleted file mode 100644 index 934842f..0000000 --- a/PersonalisationGroups.Core/Criteria/NumberOfVisits/NumberOfVisitsPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.NumberOfVisits; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.NumberOfVisits -{ - /// - /// Implements a personalisation group criteria based on the whether certain pages (node Ids) have been viewed - /// - public class NumberOfVisitsPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - internal static string CriteriaAlias = "numberOfVisits"; - - private readonly INumberOfVisitsProvider _numberOfVisitsProvider; - - public NumberOfVisitsPersonalisationGroupCriteria(INumberOfVisitsProvider numberOfVisitsProvider) - { - _numberOfVisitsProvider = numberOfVisitsProvider; - } - - public string Name => "Number of visits"; - - public string Alias => CriteriaAlias; - - public string Description => "Matches visitor session with the number of times they have visited the site"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - NumberOfVisitsSetting numberOfVisitsSetting; - try - { - numberOfVisitsSetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var timesVisited = _numberOfVisitsProvider.GetNumberOfVisits(); - - switch (numberOfVisitsSetting.Match) - { - case NumberOfVisitsSettingMatch.MoreThan: - return timesVisited > numberOfVisitsSetting.Number; - case NumberOfVisitsSettingMatch.LessThan: - return timesVisited < numberOfVisitsSetting.Number; - case NumberOfVisitsSettingMatch.Exactly: - return timesVisited == numberOfVisitsSetting.Number; - default: - return false; - } - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/NumberOfVisits/NumberOfVisitsSetting.cs b/PersonalisationGroups.Core/Criteria/NumberOfVisits/NumberOfVisitsSetting.cs deleted file mode 100644 index 150463f..0000000 --- a/PersonalisationGroups.Core/Criteria/NumberOfVisits/NumberOfVisitsSetting.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.NumberOfVisits -{ - public enum NumberOfVisitsSettingMatch - { - MoreThan, - LessThan, - Exactly, - } - - public class NumberOfVisitsSetting - { - public NumberOfVisitsSettingMatch Match { get; set; } - - public int Number { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/PagesViewed/PagesViewedPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/PagesViewed/PagesViewedPersonalisationGroupCriteria.cs deleted file mode 100644 index 402e91c..0000000 --- a/PersonalisationGroups.Core/Criteria/PagesViewed/PagesViewedPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,66 +0,0 @@ -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Extensions; -using Our.Umbraco.PersonalisationGroups.Core.Providers.PagesViewed; -using System; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.PagesViewed -{ - /// - /// Implements a personalisation group criteria based on the whether certain pages (node Ids) have been viewed - /// - public class PagesViewedPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - internal static string CriteriaAlias = "pagesViewed"; - - private readonly IPagesViewedProvider _pagesViewedProvider; - - public PagesViewedPersonalisationGroupCriteria(IPagesViewedProvider pagesViewedProvider) - { - _pagesViewedProvider = pagesViewedProvider; - } - - public string Name => "Pages viewed"; - - public string Alias => CriteriaAlias; - - public string Description => "Matches visitor session with whether certain pages have been viewed"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - PagesViewedSetting pagesViewedSetting; - try - { - pagesViewedSetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var nodeIdsViewed = _pagesViewedProvider.GetNodeIdsViewed(); - - switch (pagesViewedSetting.Match) - { - case PagesViewedSettingMatch.ViewedAny: - return nodeIdsViewed - .ContainsAny(pagesViewedSetting.NodeIds); - case PagesViewedSettingMatch.ViewedAll: - return nodeIdsViewed - .ContainsAll(pagesViewedSetting.NodeIds); - case PagesViewedSettingMatch.NotViewedAny: - return !nodeIdsViewed - .ContainsAny(pagesViewedSetting.NodeIds); - case PagesViewedSettingMatch.NotViewedAll: - return !nodeIdsViewed - .ContainsAll(pagesViewedSetting.NodeIds); - default: - return false; - } - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/PagesViewed/PagesViewedSetting.cs b/PersonalisationGroups.Core/Criteria/PagesViewed/PagesViewedSetting.cs deleted file mode 100644 index 13f5f86..0000000 --- a/PersonalisationGroups.Core/Criteria/PagesViewed/PagesViewedSetting.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.PagesViewed -{ - public enum PagesViewedSettingMatch - { - ViewedAny, - ViewedAll, - NotViewedAny, - NotViewedAll, - } - - public class PagesViewedSetting - { - public PagesViewedSettingMatch Match { get; set; } - - public int[] NodeIds { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/PersonalisationGroupCriteriaBase.cs b/PersonalisationGroups.Core/Criteria/PersonalisationGroupCriteriaBase.cs deleted file mode 100644 index a31748b..0000000 --- a/PersonalisationGroups.Core/Criteria/PersonalisationGroupCriteriaBase.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System; -using System.Globalization; -using System.Text.RegularExpressions; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria -{ - /// - /// Provides common base functionality for personalisation criteria - /// - public abstract class PersonalisationGroupCriteriaBase - { - public virtual string ClientAssetsFolder => "PersonalisationGroups/Criteria"; - - protected static bool MatchesValue(string valueFromContext, string valueFromDefinition) - { - if (valueFromContext == null) - { - return false; - } - - return string.Equals(valueFromContext, valueFromDefinition, - StringComparison.InvariantCultureIgnoreCase); - } - - protected static bool ContainsValue(string valueFromContext, string valueFromDefinition) - { - if (valueFromContext == null) - { - return false; - } - - return CultureInfo.InvariantCulture.CompareInfo - .IndexOf(valueFromContext, valueFromDefinition, CompareOptions.IgnoreCase) >= 0; - } - - protected static bool MatchesRegex(string valueFromContext, string valueFromDefinition) - { - if (valueFromContext == null) - { - return false; - } - - return Regex.IsMatch(valueFromContext, valueFromDefinition); - } - - protected bool CompareValues(string value, string definitionValue, Comparison comparison) - { - var result = DateCompare(value, definitionValue, comparison, out bool comparisonMade); - if (comparisonMade) - { - return result; - } - result = NumericCompare(value, definitionValue, comparison, out comparisonMade); - - if (comparisonMade) - { - return result; - } - - return StringCompare(value, definitionValue, comparison); - } - - private static bool DateCompare(string value, string definitionValue, Comparison comparison, out bool comparisonMade) - { - if (DateTime.TryParse(value, out DateTime dateValue) && DateTime.TryParse(definitionValue, out DateTime dateDefinitionValue)) - { - comparisonMade = true; - switch (comparison) - { - case Comparison.GreaterThan: - return dateValue > dateDefinitionValue; - case Comparison.GreaterThanOrEqual: - return dateValue >= dateDefinitionValue; - case Comparison.LessThan: - return dateValue < dateDefinitionValue; - case Comparison.LessThanOrEqual: - return dateValue <= dateDefinitionValue; - } - } - - comparisonMade = false; - return false; - } - - private static bool NumericCompare(string value, string definitionValue, Comparison comparison, out bool comparisonMade) - { - decimal decimalValue; - if (decimal.TryParse(value, out decimalValue) && decimal.TryParse(definitionValue, out decimal decimalDefinitionValue)) - { - comparisonMade = true; - switch (comparison) - { - case Comparison.GreaterThan: - return decimalValue > decimalDefinitionValue; - case Comparison.GreaterThanOrEqual: - return decimalValue >= decimalDefinitionValue; - case Comparison.LessThan: - return decimalValue < decimalDefinitionValue; - case Comparison.LessThanOrEqual: - return decimalValue <= decimalDefinitionValue; - } - } - - comparisonMade = false; - return false; - } - - private static bool StringCompare(string value, string definitionValue, Comparison comparison) - { - var comparisonValue = string.Compare(value, definitionValue, StringComparison.InvariantCultureIgnoreCase); - switch (comparison) - { - case Comparison.GreaterThan: - return comparisonValue > 0; - case Comparison.GreaterThanOrEqual: - return comparisonValue >= 0; - case Comparison.LessThan: - return comparisonValue < 0; - case Comparison.LessThanOrEqual: - return comparisonValue <= 0; - } - - return false; - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/PersonalisationGroupDefinition.cs b/PersonalisationGroups.Core/Criteria/PersonalisationGroupDefinition.cs deleted file mode 100644 index dabd58d..0000000 --- a/PersonalisationGroups.Core/Criteria/PersonalisationGroupDefinition.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria -{ - using System.Collections.Generic; - - public enum PersonalisationGroupDefinitionMatch - { - All, - Any - } - - public enum PersonalisationGroupDefinitionDuration - { - Page, - Session, - Visitor - } - - /// - /// The definition of a personalisation group - /// - public class PersonalisationGroupDefinition - { - public PersonalisationGroupDefinitionMatch Match { get; set; } - - public PersonalisationGroupDefinitionDuration Duration { get; set; } - - public int Score { get; set; } - - public IEnumerable Details { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/PersonalisationGroupDefinitionDetail.cs b/PersonalisationGroups.Core/Criteria/PersonalisationGroupDefinitionDetail.cs deleted file mode 100644 index cdbc38e..0000000 --- a/PersonalisationGroups.Core/Criteria/PersonalisationGroupDefinitionDetail.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria -{ - /// - /// The definition of a detail record for a personalisation group - /// - public class PersonalisationGroupDefinitionDetail - { - public string Alias { get; set; } - - public string Definition { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Querystring/QuerystringPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/Querystring/QuerystringPersonalisationGroupCriteria.cs deleted file mode 100644 index a176389..0000000 --- a/PersonalisationGroups.Core/Criteria/Querystring/QuerystringPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Querystring; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Querystring -{ - public class QuerystringPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IQuerystringProvider _querystringProvider; - - public QuerystringPersonalisationGroupCriteria(IQuerystringProvider querystringProvider) - { - _querystringProvider = querystringProvider; - } - - public string Alias => "querystring"; - - public string Name => "Querystring"; - - public string Description => "Matches visitor based on specific values in the Querystring"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - QuerystringSetting querystringSetting; - try - { - querystringSetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var querystring = _querystringProvider.GetQuerystring(); - - var querystringValue = querystring[querystringSetting.Key]; - - switch (querystringSetting.Match) - { - case QuerystringSettingMatch.MatchesValue: - return MatchesValue(querystringValue, querystringSetting.Value); - case QuerystringSettingMatch.DoesNotMatchValue: - return !MatchesValue(querystringValue, querystringSetting.Value); - case QuerystringSettingMatch.ContainsValue: - return ContainsValue(querystringValue, querystringSetting.Value); - case QuerystringSettingMatch.DoesNotContainValue: - return !ContainsValue(querystringValue, querystringSetting.Value); - case QuerystringSettingMatch.GreaterThanValue: - case QuerystringSettingMatch.GreaterThanOrEqualToValue: - case QuerystringSettingMatch.LessThanValue: - case QuerystringSettingMatch.LessThanOrEqualToValue: - return CompareValues(querystringValue, querystringSetting.Value, GetComparison(querystringSetting.Match)); - case QuerystringSettingMatch.MatchesRegex: - return MatchesRegex(querystringValue, querystringSetting.Value); - case QuerystringSettingMatch.DoesNotMatchRegex: - return !MatchesRegex(querystringValue, querystringSetting.Value); - default: - return false; - } - } - - private static Comparison GetComparison(QuerystringSettingMatch settingMatch) - { - switch (settingMatch) - { - case QuerystringSettingMatch.GreaterThanValue: - return Comparison.GreaterThan; - case QuerystringSettingMatch.GreaterThanOrEqualToValue: - return Comparison.GreaterThanOrEqual; - case QuerystringSettingMatch.LessThanValue: - return Comparison.LessThan; - case QuerystringSettingMatch.LessThanOrEqualToValue: - return Comparison.LessThanOrEqual; - default: - throw new ArgumentException("Setting supplied does not match a comparison type", nameof(settingMatch)); - } - } - } -} \ No newline at end of file diff --git a/PersonalisationGroups.Core/Criteria/Querystring/QuerystringSetting.cs b/PersonalisationGroups.Core/Criteria/Querystring/QuerystringSetting.cs deleted file mode 100644 index 46e97d4..0000000 --- a/PersonalisationGroups.Core/Criteria/Querystring/QuerystringSetting.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Querystring -{ - public enum QuerystringSettingMatch - { - MatchesValue, - DoesNotMatchValue, - ContainsValue, - DoesNotContainValue, - GreaterThanValue, - GreaterThanOrEqualToValue, - LessThanValue, - LessThanOrEqualToValue, - MatchesRegex, - DoesNotMatchRegex, - } - - public class QuerystringSetting - { - public string Key { get; set; } - - public QuerystringSettingMatch Match { get; set; } - - public string Value { get; set; } - } -} \ No newline at end of file diff --git a/PersonalisationGroups.Core/Criteria/Referral/ReferralPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/Referral/ReferralPersonalisationGroupCriteria.cs deleted file mode 100644 index 80fe7d8..0000000 --- a/PersonalisationGroups.Core/Criteria/Referral/ReferralPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Referrer; -using System; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Referral -{ - /// - /// Implements a personalisation group criteria based on the presence, absence or value of a session key - /// - public class ReferralPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IReferrerProvider _referrerProvider; - - public ReferralPersonalisationGroupCriteria(IReferrerProvider referrerProvider) - { - _referrerProvider = referrerProvider; - } - - public string Name => "Referral"; - - public string Alias => "referral"; - - public string Description => "Matches visitor with a referral URL"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - ReferralSetting referralSetting; - try - { - referralSetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var referrer = _referrerProvider.GetReferrer(); - switch (referralSetting.Match) - { - case ReferralSettingMatch.MatchesValue: - return MatchesValue(referrer, referralSetting.Value); - case ReferralSettingMatch.DoesNotMatchValue: - return !MatchesValue(referrer, referralSetting.Value); - case ReferralSettingMatch.ContainsValue: - return ContainsValue(referrer, referralSetting.Value); - case ReferralSettingMatch.DoesNotContainValue: - return !ContainsValue(referrer, referralSetting.Value); - default: - return false; - } - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Referral/ReferralSetting.cs b/PersonalisationGroups.Core/Criteria/Referral/ReferralSetting.cs deleted file mode 100644 index 7ce2e12..0000000 --- a/PersonalisationGroups.Core/Criteria/Referral/ReferralSetting.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Referral -{ - public enum ReferralSettingMatch - { - MatchesValue, - DoesNotMatchValue, - ContainsValue, - DoesNotContainValue, - } - - public class ReferralSetting - { - public ReferralSettingMatch Match { get; set; } - - public string Value { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Region/RegionPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/Region/RegionPersonalisationGroupCriteria.cs deleted file mode 100644 index a905d09..0000000 --- a/PersonalisationGroups.Core/Criteria/Region/RegionPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,86 +0,0 @@ -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Ip; -using System; -using System.Linq; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Region -{ - /// - /// Implements a personalisation group criteria based on the country derived from the vistor's IP address - /// - public class RegionPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IIpProvider _ipProvider; - private readonly IGeoLocationProvider _geoLocationProvider; - - public RegionPersonalisationGroupCriteria(IIpProvider ipProvider, IGeoLocationProvider geoLocationProvider) - { - _ipProvider = ipProvider; - _geoLocationProvider = geoLocationProvider; - } - - public string Name => "Region"; - - public string Alias => "region"; - - public string Description => "Matches visitor region derived from their IP address to a given list of regions"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - RegionSetting regionSetting; - try - { - regionSetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - var ip = _ipProvider.GetIp(); - if (!string.IsNullOrEmpty(ip)) - { - var country = _geoLocationProvider.GetCountryFromIp(ip); - if (country != null) - { - if (regionSetting.Match == GeoLocationSettingMatch.CouldNotBeLocated) - { - // We can't locate, so return false. - return false; - } - - var matchedCountry = string.Equals(regionSetting.CountryCode, country.Code, StringComparison.InvariantCultureIgnoreCase); - var matchedRegion = false; - if (matchedCountry) - { - var region = _geoLocationProvider.GetRegionFromIp(ip); - if (region != null) - { - matchedRegion = regionSetting.Names - .Intersect(region.GetAllNames(), StringComparer.OrdinalIgnoreCase) - .Any(); - } - } - - switch (regionSetting.Match) - { - case GeoLocationSettingMatch.IsLocatedIn: - return matchedRegion; - case GeoLocationSettingMatch.IsNotLocatedIn: - return !matchedRegion; - default: - return false; - } - } - } - - return regionSetting.Match == GeoLocationSettingMatch.CouldNotBeLocated; - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Region/RegionSetting.cs b/PersonalisationGroups.Core/Criteria/Region/RegionSetting.cs deleted file mode 100644 index d78cff9..0000000 --- a/PersonalisationGroups.Core/Criteria/Region/RegionSetting.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Region -{ - using System.Collections.Generic; - - public class RegionSetting - { - public GeoLocationSettingMatch Match { get; set; } - - public string CountryCode { get; set; } - - public List Names { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Session/SessionPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/Session/SessionPersonalisationGroupCriteria.cs deleted file mode 100644 index 8993967..0000000 --- a/PersonalisationGroups.Core/Criteria/Session/SessionPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,97 +0,0 @@ -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Session; -using System; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Session -{ - /// - /// Implements a personalisation group criteria based on the presence, absence or value of a session key - /// - public class SessionPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly ISessionProvider _sessionProvider; - - public SessionPersonalisationGroupCriteria(ISessionProvider sessionProvider) - { - _sessionProvider = sessionProvider; - } - - public string Name => "Session"; - - public string Alias => "session"; - - public string Description => "Matches visitor session with the presence, absence or value of a session key"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - SessionSetting sessionSetting; - try - { - sessionSetting = JsonConvert.DeserializeObject(definition); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - - if (string.IsNullOrEmpty(sessionSetting.Key)) - { - throw new ArgumentNullException("key", "Session key not set"); - } - - var keyExists = _sessionProvider.KeyExists(sessionSetting.Key); - var value = string.Empty; - if (keyExists) - { - value = _sessionProvider.GetValue(sessionSetting.Key); - } - - switch (sessionSetting.Match) - { - case SessionSettingMatch.Exists: - return keyExists; - case SessionSettingMatch.DoesNotExist: - return !keyExists; - case SessionSettingMatch.MatchesValue: - return keyExists && MatchesValue(value, sessionSetting.Value); - case SessionSettingMatch.ContainsValue: - return keyExists && ContainsValue(value, sessionSetting.Value); - case SessionSettingMatch.GreaterThanValue: - case SessionSettingMatch.GreaterThanOrEqualToValue: - case SessionSettingMatch.LessThanValue: - case SessionSettingMatch.LessThanOrEqualToValue: - return keyExists && - CompareValues(value, sessionSetting.Value, GetComparison(sessionSetting.Match)); - case SessionSettingMatch.MatchesRegex: - return keyExists && MatchesRegex(value, sessionSetting.Value); - case SessionSettingMatch.DoesNotMatchRegex: - return !keyExists || !MatchesRegex(value, sessionSetting.Value); - - default: - return false; - } - } - - private static Comparison GetComparison(SessionSettingMatch settingMatch) - { - switch (settingMatch) - { - case SessionSettingMatch.GreaterThanValue: - return Comparison.GreaterThan; - case SessionSettingMatch.GreaterThanOrEqualToValue: - return Comparison.GreaterThanOrEqual; - case SessionSettingMatch.LessThanValue: - return Comparison.LessThan; - case SessionSettingMatch.LessThanOrEqualToValue: - return Comparison.LessThanOrEqual; - default: - throw new ArgumentException("Setting supplied does not match a comparison type", nameof(settingMatch)); - } - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/Session/SessionSetting.cs b/PersonalisationGroups.Core/Criteria/Session/SessionSetting.cs deleted file mode 100644 index 5f81be2..0000000 --- a/PersonalisationGroups.Core/Criteria/Session/SessionSetting.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.Session -{ - public enum SessionSettingMatch - { - Exists, - DoesNotExist, - MatchesValue, - ContainsValue, - GreaterThanValue, - GreaterThanOrEqualToValue, - LessThanValue, - LessThanOrEqualToValue, - MatchesRegex, - DoesNotMatchRegex, - } - - public class SessionSetting - { - public string Key { get; set; } - - public SessionSettingMatch Match { get; set; } - - public string Value { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Criteria/TimeOfDay/TimeOfDayPersonalisationGroupCriteria.cs b/PersonalisationGroups.Core/Criteria/TimeOfDay/TimeOfDayPersonalisationGroupCriteria.cs deleted file mode 100644 index 7ddd75b..0000000 --- a/PersonalisationGroups.Core/Criteria/TimeOfDay/TimeOfDayPersonalisationGroupCriteria.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Providers.DateTime; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.TimeOfDay -{ - /// - /// Implements a personalisation group criteria based on the time of the day - /// - public class TimeOfDayPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria - { - private readonly IDateTimeProvider _dateTimeProvider; - - public TimeOfDayPersonalisationGroupCriteria(IDateTimeProvider dateTimeProvider) - { - _dateTimeProvider = dateTimeProvider; - } - - public string Name => "Time of day"; - - public string Alias => "timeOfDay"; - - public string Description => "Matches visitor session with defined times of the day"; - - public bool MatchesVisitor(string definition) - { - if (string.IsNullOrEmpty(definition)) - { - throw new ArgumentNullException(nameof(definition)); - } - - try - { - var definedTimesOfDay = JsonConvert.DeserializeObject>(definition); - var now = int.Parse(_dateTimeProvider.GetCurrentDateTime().ToString("HHmm")); - return definedTimesOfDay - .Any(x => x.From <= now && x.To >= now); - } - catch (JsonReaderException) - { - throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); - } - } - } -} diff --git a/PersonalisationGroups.Core/Criteria/TimeOfDay/TimeOfDaySetting.cs b/PersonalisationGroups.Core/Criteria/TimeOfDay/TimeOfDaySetting.cs deleted file mode 100644 index c55d268..0000000 --- a/PersonalisationGroups.Core/Criteria/TimeOfDay/TimeOfDaySetting.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Criteria.TimeOfDay -{ - public class TimeOfDaySetting - { - public int From { get; set; } - - public int To { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Extensions/AssemblyExtensions.cs b/PersonalisationGroups.Core/Extensions/AssemblyExtensions.cs deleted file mode 100644 index eae51a7..0000000 --- a/PersonalisationGroups.Core/Extensions/AssemblyExtensions.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; - -namespace Our.Umbraco.PersonalisationGroups.Core.Extensions -{ - /// - /// Provides extension methods to Assembly - /// - public static class AssemblyExtensions - { - /// - /// Safely loads types from a given assembly avoiding errors if certain ones can't be loaded - /// - /// Instance of an Assembly - /// Enumerable of loadable types - /// - /// For further on this and why needed, see: http://haacked.com/archive/2012/07/23/get-all-types-in-an-assembly.aspx/ - /// - public static IEnumerable GetLoadableTypes(this Assembly assembly) - { - try - { - return assembly.GetTypes(); - } - catch (ReflectionTypeLoadException e) - { - return e.Types.Where(t => t != null); - } - } - } -} diff --git a/PersonalisationGroups.Core/Extensions/EnumerableExtensions.cs b/PersonalisationGroups.Core/Extensions/EnumerableExtensions.cs deleted file mode 100644 index ab98f5b..0000000 --- a/PersonalisationGroups.Core/Extensions/EnumerableExtensions.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Our.Umbraco.PersonalisationGroups.Core.Extensions -{ - public static class EnumerableExtensions - { - public static bool ContainsAll(this IEnumerable source, IEnumerable other) => !other.Except(source).Any(); - - public static bool ContainsAny(this IEnumerable source, IEnumerable other) => other.Any(source.Contains); - } -} diff --git a/PersonalisationGroups.Core/Extensions/PublishedElementExtensions.cs b/PersonalisationGroups.Core/Extensions/PublishedElementExtensions.cs deleted file mode 100644 index fd166fa..0000000 --- a/PersonalisationGroups.Core/Extensions/PublishedElementExtensions.cs +++ /dev/null @@ -1,105 +0,0 @@ -using Our.Umbraco.PersonalisationGroups.Core.Services; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Web.Common; - -namespace Umbraco.Extensions -{ - /// - /// Provides extension methods to IPublishedContent - /// - public static class PublishedContentExtensions - { - /// - /// Adds an extension method to IPublishedContent to determine if the content item should be shown to the current site - /// visitor, based on the personalisation groups associated with it. - /// - /// Instance of IPublishedContent - /// The group matching service - /// Indicates the response to return if groups cannot be found on the content - /// True if content should be shown to visitor - public static bool ShowToVisitor(this IPublishedElement content, IGroupMatchingService groupMatchingService, bool showIfNoGroupsDefined = true) - { - var pickedGroups = groupMatchingService.GetPickedGroups(content); - return ShowToVisitor(groupMatchingService, pickedGroups, showIfNoGroupsDefined); - } - - /// - /// Adds an extension method to IPublishedContent to score the content item for the current site - /// visitor, based on the personalisation groups associated with it. - /// - /// Instance of IPublishedContent - /// The group matching service - /// True if content should be shown to visitor - public static int ScoreForVisitor(this IPublishedElement content, IGroupMatchingService groupMatchingService) - { - var pickedGroups = groupMatchingService.GetPickedGroups(content); - return ScoreForVisitor(groupMatchingService, pickedGroups); - } - - /// - /// Adds an extension method to UmbracoHelper to determine if the content item should be shown to the current site - /// visitor, based on the personalisation groups associated with the Ids passed into the method - /// - /// Instance of UmbracoHelper - /// The group matching service - /// List of group Ids - /// Indicates the response to return if groups cannot be found on the content - /// True if content should be shown to visitor - public static bool ShowToVisitor(this UmbracoHelper umbraco, IGroupMatchingService groupMatchingService, IEnumerable groupIds, bool showIfNoGroupsDefined = true) - { - var groups = umbraco.Content(groupIds).ToList(); - return ShowToVisitor(groupMatchingService, groups, showIfNoGroupsDefined); - } - - /// - /// Adds an extension method to UmbracoHelper to score the content item for the current site - /// visitor, based on the personalisation groups associated with the Ids passed into the method - /// - /// Instance of UmbracoHelper - /// The group matching service - /// List of group Ids - /// True if content should be shown to visitor - public static int ScoreForVisitor(this UmbracoHelper umbraco, IGroupMatchingService groupMatchingService, IEnumerable groupIds) - { - var groups = umbraco.Content(groupIds).ToList(); - return ScoreForVisitor(groupMatchingService, groups); - } - - /// - /// Determines if the content item should be shown to the current site visitor, based on the personalisation groups associated with it. - /// - /// The group matching service - /// List of IPublishedContent items that are the groups you want to check against. - /// Indicates the response to return if groups cannot be found on the content - /// True if content should be shown to visitor - private static bool ShowToVisitor(IGroupMatchingService groupMatchingService, IList pickedGroups, bool showIfNoGroupsDefined = true) - { - if (!pickedGroups.Any()) - { - // No personalisation groups picked or no property for picker, so we return the provided default - return showIfNoGroupsDefined; - } - - return groupMatchingService.MatchGroups(pickedGroups); - } - - /// - /// Scores the content item for the current site visitor, based on the personalisation groups associated with it. - /// - /// The group matching service - /// List of IPublishedContent items that are the groups you want to check against. - /// True if content should be shown to visitor - private static int ScoreForVisitor(IGroupMatchingService groupMatchingService, IList pickedGroups) - { - if (!pickedGroups.Any()) - { - // No personalisation groups picked or no property for picker, so we score zero - return 0; - } - - return groupMatchingService.ScoreGroups(pickedGroups); - } - } -} diff --git a/PersonalisationGroups.Core/Extensions/StringExtensions.cs b/PersonalisationGroups.Core/Extensions/StringExtensions.cs deleted file mode 100644 index 63333b0..0000000 --- a/PersonalisationGroups.Core/Extensions/StringExtensions.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; - -namespace Our.Umbraco.PersonalisationGroups.Core.Extensions -{ - public static class StringExtensions - { - public static bool InvariantEquals(this string compare, string compareTo) => compare.Equals(compareTo, StringComparison.InvariantCultureIgnoreCase); - - public static bool InvariantEndsWith(this string compare, string compareTo) => compare.EndsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); - - public static string TrimStart(this string value, string forRemoving) - { - if (string.IsNullOrEmpty(value)) - { - return value; - } - - if (string.IsNullOrEmpty(forRemoving)) - { - return value; - } - - while (value.StartsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) - { - value = value.Substring(forRemoving.Length); - } - - return value; - } - - public static string TrimEnd(this string value, string forRemoving) - { - if (string.IsNullOrEmpty(value)) - { - return value; - } - - if (string.IsNullOrEmpty(forRemoving)) - { - return value; - } - - while (value.EndsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) - { - value = value.Remove(value.LastIndexOf(forRemoving, StringComparison.InvariantCultureIgnoreCase)); - } - - return value; - } - - public static string[] SplitByNewLine(this string text, StringSplitOptions options) => text.Split(new[] { "\r\n", "\r", "\n" }, options); - } -} diff --git a/PersonalisationGroups.Core/Extensions/UmbracoHelperExtensions.cs b/PersonalisationGroups.Core/Extensions/UmbracoHelperExtensions.cs deleted file mode 100644 index 7f053b8..0000000 --- a/PersonalisationGroups.Core/Extensions/UmbracoHelperExtensions.cs +++ /dev/null @@ -1,167 +0,0 @@ -using Our.Umbraco.PersonalisationGroups.Core; -using Our.Umbraco.PersonalisationGroups.Core.Criteria; -using Our.Umbraco.PersonalisationGroups.Core.Services; -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Web.Common; - -namespace Umbraco.Extensions -{ - /// - /// Provides extension methods to UmbracoHelper - /// - public static class UmbracoHelperExtensions - { - /// - /// Adds an extension method to UmbracoHelper to determine if the current site - /// visitor matches a single personalisation group. - /// - /// Instance of UmbracoHelper - /// The group matching service. - /// Name of group node to match - /// True if visitor matches group - public static bool MatchesGroup(this UmbracoHelper helper, IGroupMatchingService groupMatchingService, string groupName) - { - return MatchesGroups(helper, groupMatchingService, new string[] { groupName }, PersonalisationGroupDefinitionMatch.Any); - } - - /// - /// Adds an extension method to UmbracoHelper to determine if the current site - /// visitor matches any of a set of personalisation groups. - /// - /// Instance of UmbracoHelper - /// The group matching service. - /// Names of group nodes to match - /// True if visitor matches any group - public static bool MatchesAnyGroup(this UmbracoHelper helper, IGroupMatchingService groupMatchingService, string[] groupNames) - { - return MatchesGroups(helper, groupMatchingService, groupNames, PersonalisationGroupDefinitionMatch.Any); - } - - /// - /// Adds an extension method to UmbracoHelper to determine if the current site - /// visitor matches all of a set of personalisation groups. - /// - /// Instance of UmbracoHelper - /// The group matching service. - /// Names of group nodes to match - /// True if visitor matches all groups - public static bool MatchesAllGroups(this UmbracoHelper helper, IGroupMatchingService groupMatchingService, string[] groupNames) - { - return MatchesGroups(helper, groupMatchingService, groupNames, PersonalisationGroupDefinitionMatch.All); - } - - private static bool MatchesGroups(this UmbracoHelper helper, IGroupMatchingService groupMatchingService, string[] groupNames, PersonalisationGroupDefinitionMatch matchType) - { - var groupsRootFolder = GetGroupsRootFolder(helper); - if (groupsRootFolder == null) - { - return false; - } - - var groups = GetGroups(groupsRootFolder); - return groupMatchingService.MatchGroupsByName(groupNames, groups, matchType); - } - - private static IPublishedContent GetGroupsRootFolder(UmbracoHelper helper) - { - return helper.ContentAtRoot() - .FirstOrDefault(x => x.IsDocumentType(AppConstants.DocumentTypeAliases.PersonalisationGroupsFolder)); - } - - private static IList GetGroups(IPublishedContent groupsRootFolder) - { - return groupsRootFolder.Descendants() - .Where(x => x.IsDocumentType(AppConstants.DocumentTypeAliases.PersonalisationGroup)) - .ToList(); - } - - /// - /// Adds an extension method to UmbracoHelper to calculate a hash for the current visitor for all visitor groups - /// - /// Instance of UmbracoHelper - /// The group matching cache - /// Provides access to the runtime cache - /// Id of root node for the personalisation groups - /// Identifier for the user to use in the cache key (likely the session Id) - /// Length of time in seconds to cache the generated personalisation group hash for the visitor - /// Has for the visitor for all groups - public static string GetPersonalisationGroupsHashForVisitor( - this UmbracoHelper helper, - IGroupMatchingService groupMatchingService, - AppCaches appCaches, - Guid personalisationGroupsRootNodeId, - string cacheUserIdentifier, - int cacheForSeconds) - { - var personalisationGroupsRootNode = helper.Content(personalisationGroupsRootNodeId); - if (!personalisationGroupsRootNode.IsDocumentType(AppConstants.DocumentTypeAliases.PersonalisationGroupsFolder)) - { - throw new InvalidOperationException( - $"The personalisation groups hash for a visitor can only be calculated for a root node of type {AppConstants.DocumentTypeAliases.PersonalisationGroupsFolder}"); - } - - return GetPersonalisationGroupsHashForVisitor(helper, groupMatchingService, appCaches, personalisationGroupsRootNode, cacheUserIdentifier, cacheForSeconds); - } - - /// - /// Adds an extension method to UmbracoHelper to calculate a hash for the current visitor for all visitor groups - /// - /// Instance of UmbracoHelper - /// The group matching cache - /// Provides access to the runtime cache - /// Id of root node for the personalisation groups - /// Identifier for the user to use in the cache key (likely the session Id) - /// Length of time in seconds to cache the generated personalisation group hash for the visitor - /// Has for the visitor for all groups - public static string GetPersonalisationGroupsHashForVisitor( - this UmbracoHelper helper, - IGroupMatchingService groupMatchingService, - AppCaches appCaches, - int personalisationGroupsRootNodeId, - string cacheUserIdentifier, - int cacheForSeconds) - { - var personalisationGroupsRootNode = helper.Content(personalisationGroupsRootNodeId); - if (!personalisationGroupsRootNode.IsDocumentType(AppConstants.DocumentTypeAliases.PersonalisationGroupsFolder)) - { - throw new InvalidOperationException( - $"The personalisation groups hash for a visitor can only be calculated for a root node of type {AppConstants.DocumentTypeAliases.PersonalisationGroupsFolder}"); - } - - return GetPersonalisationGroupsHashForVisitor(helper, groupMatchingService, appCaches, personalisationGroupsRootNode, cacheUserIdentifier, cacheForSeconds); - } - - /// - /// Adds an extension method to UmbracoHelper to calculate a hash for the current visitor for all visitor groups - /// - /// Instance of UmbracoHelper - /// The group matching cache - /// Provides access to the runtime cache - /// Root node for the personalisation groups - /// Identifier for the user to use in the cache key (likely the session Id) - /// Length of time in seconds to cache the generated personalisation group hash for the visitor - /// Has for the visitor for all groups - public static string GetPersonalisationGroupsHashForVisitor( - this UmbracoHelper helper, - IGroupMatchingService groupMatchingService, - AppCaches appCaches, - IPublishedContent personalisationGroupsRootNode, - string cacheUserIdentifier, - int cacheForSeconds) - { - if (personalisationGroupsRootNode == null) - { - throw new ArgumentNullException(nameof(personalisationGroupsRootNode)); - } - - var cacheKey = $"{cacheUserIdentifier}-{AppConstants.CacheKeys.PersonalisationGroupsVisitorHash}"; - return appCaches.RuntimeCache.GetCacheItem(cacheKey, - () => groupMatchingService.CreatePersonalisationGroupsHashForVisitor(personalisationGroupsRootNode), - timeout: TimeSpan.FromSeconds(cacheForSeconds)); - } - } -} diff --git a/PersonalisationGroups.Core/Middleware/TrackUserActivityMiddleware.cs b/PersonalisationGroups.Core/Middleware/TrackUserActivityMiddleware.cs deleted file mode 100644 index fb1d5b4..0000000 --- a/PersonalisationGroups.Core/Middleware/TrackUserActivityMiddleware.cs +++ /dev/null @@ -1,50 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Our.Umbraco.PersonalisationGroups.Core.Services; -using System; -using System.Threading.Tasks; -using Umbraco.Cms.Core.Web; -using Umbraco.Extensions; - -namespace Our.Umbraco.PersonalisationGroups.Core.Middleware -{ - internal class TrackUserActivityMiddleware : IMiddleware - { - private readonly IUmbracoContextAccessor _umbracoContextAccessor; - private readonly IUserActivityTracker _userActivityTracker; - - public TrackUserActivityMiddleware(IUmbracoContextAccessor umbracoContextAccessor, IUserActivityTracker userActivityTracker) - { - _umbracoContextAccessor = umbracoContextAccessor; - _userActivityTracker = userActivityTracker; - } - - public async Task InvokeAsync(HttpContext context, RequestDelegate next) - { - if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) - { - await next(context); - return ; - } - - var isFrontEndRequest = umbracoContext.IsFrontEndUmbracoRequest(); - if (!isFrontEndRequest) - { - await next(context); - return; - } - - var pageId = umbracoContext.PublishedRequest?.PublishedContent?.Id; - if (pageId == null) - { - await next(context); - return; - } - - _userActivityTracker.TrackPageView(pageId.Value); - - _userActivityTracker.TrackSession(); - - await next(context); - } - } -} diff --git a/PersonalisationGroups.Core/Migrations/PersonalisationGroupsPackageMigrationPlan.cs b/PersonalisationGroups.Core/Migrations/PersonalisationGroupsPackageMigrationPlan.cs deleted file mode 100644 index db99efe..0000000 --- a/PersonalisationGroups.Core/Migrations/PersonalisationGroupsPackageMigrationPlan.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Umbraco.Cms.Infrastructure.Packaging; - -namespace Our.Umbraco.PersonalisationGroups.Core.Migrations -{ - public class PersonalisationGroupsPackageMigrationPlan : AutomaticPackageMigrationPlan - { - public PersonalisationGroupsPackageMigrationPlan() - : base(AppConstants.PackageName) - { - } - } -} diff --git a/PersonalisationGroups.Core/Migrations/package.xml b/PersonalisationGroups.Core/Migrations/package.xml deleted file mode 100644 index 092d4be..0000000 --- a/PersonalisationGroups.Core/Migrations/package.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - Personalisation Groups - - - - - - - - - - - Personalisation Group - personalisationGroup - a960545f-a794-412d-996a-8f9a7da522c4 - icon-operator color-green - folder.png - - False - False - False - Nothing - - - - - - - - Group definition - definition - 167df64e-ad8a-4af1-b0c6-a39dfe21820d - personalisationGroupDefinition - 56f6775d-e85f-4863-819d-fbf890bc6666 - Settings - 0 - False - False - Nothing - - - - - 14 - Settings - 0 - - - - - - Personalisation Groups Folder - personalisationGroupsFolder - 46b8efcf-a31d-43bf-b5b6-bea0e42bed41 - icon-folder-close color-green - folder.png - - True - False - False - Nothing - - - - - - personalisationGroup - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/PersonalisationGroups.Core/PropertyEditors/PersonalisationGroupDefinitionPropertyEditor.cs b/PersonalisationGroups.Core/PropertyEditors/PersonalisationGroupDefinitionPropertyEditor.cs deleted file mode 100644 index d7e2014..0000000 --- a/PersonalisationGroups.Core/PropertyEditors/PersonalisationGroupDefinitionPropertyEditor.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Umbraco.Cms.Core.PropertyEditors; -using Constants = Our.Umbraco.PersonalisationGroups.Core.AppConstants; - -namespace Our.Umbraco.PersonalisationGroups.Core.PropertyEditors -{ - /// - /// Property editor for managing the definition of a personalisation group. - /// - [DataEditor( - alias: Constants.PersonalisationGroupDefinitionPropertyEditorAlias, - name: "Personalisation group definition", - view: "/App_Plugins/PersonalisationGroups/personalisation-group-definition.html", - Icon = "icon-operator", - ValueType = "JSON")] - public class PersonalisationGroupDefinitionPropertyEditor : DataEditor - { - public PersonalisationGroupDefinitionPropertyEditor(IDataValueEditorFactory dataValueEditorFactory) - : base(dataValueEditorFactory) - { - } - } -} diff --git a/PersonalisationGroups.Core/PropertyValueConverters/PersonalisationGroupDefinitionPropertyValueConverter.cs b/PersonalisationGroups.Core/PropertyValueConverters/PersonalisationGroupDefinitionPropertyValueConverter.cs deleted file mode 100644 index fc06f35..0000000 --- a/PersonalisationGroups.Core/PropertyValueConverters/PersonalisationGroupDefinitionPropertyValueConverter.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using Newtonsoft.Json; -using Our.Umbraco.PersonalisationGroups.Core.Criteria; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.PropertyEditors; - -namespace Our.Umbraco.PersonalisationGroups.Core.PropertyValueConverters -{ - /// - /// Property converter to convert the saved JSON representation of a personalisation group definition to the - /// strongly typed model. - /// - public class PersonalisationGroupDefinitionPropertyValueConverter : PropertyValueConverterBase - { - public override Type GetPropertyValueType(IPublishedPropertyType propertyType) - => typeof(PersonalisationGroupDefinition); - - public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) - => PropertyCacheLevel.Element; - - public override bool IsConverter(IPublishedPropertyType propertyType) - { - return propertyType.EditorAlias.Equals(AppConstants.PersonalisationGroupDefinitionPropertyEditorAlias); - } - - public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) - { - return source; - } - - public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) - { - if (inter == null) - { - return null; - } - - return JsonConvert.DeserializeObject(inter.ToString()); - } - } -} \ No newline at end of file diff --git a/PersonalisationGroups.Core/Providers/AuthenticationStatus/HttpContextAuthenticationStatusProvider.cs b/PersonalisationGroups.Core/Providers/AuthenticationStatus/HttpContextAuthenticationStatusProvider.cs deleted file mode 100644 index 23456cc..0000000 --- a/PersonalisationGroups.Core/Providers/AuthenticationStatus/HttpContextAuthenticationStatusProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.AuthenticationStatus -{ - public class HttpContextAuthenticationStatusProvider : IAuthenticationStatusProvider - { - private readonly IHttpContextAccessor _httpContextAccessor; - - public HttpContextAuthenticationStatusProvider(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - public bool IsAuthenticated() - { - return _httpContextAccessor.HttpContext.User.Identity.IsAuthenticated; - } - } -} diff --git a/PersonalisationGroups.Core/Providers/AuthenticationStatus/IAuthenticationStatusProvider.cs b/PersonalisationGroups.Core/Providers/AuthenticationStatus/IAuthenticationStatusProvider.cs deleted file mode 100644 index a833a0b..0000000 --- a/PersonalisationGroups.Core/Providers/AuthenticationStatus/IAuthenticationStatusProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.AuthenticationStatus -{ - public interface IAuthenticationStatusProvider - { - bool IsAuthenticated(); - } -} diff --git a/PersonalisationGroups.Core/Providers/Cookie/HttpContextCookieProvider.cs b/PersonalisationGroups.Core/Providers/Cookie/HttpContextCookieProvider.cs deleted file mode 100644 index 35f3f57..0000000 --- a/PersonalisationGroups.Core/Providers/Cookie/HttpContextCookieProvider.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using System; -using Umbraco.Cms.Core.Web; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Cookie -{ - public class HttpContextCookieProvider : ICookieProvider - { - private readonly PersonalisationGroupsConfig _config; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ISessionManager _sessionManager; - - public HttpContextCookieProvider(IOptions config, IHttpContextAccessor httpContextAccessor, ISessionManager sessionManager) - { - _config = config.Value; - _httpContextAccessor = httpContextAccessor; - _sessionManager = sessionManager; - } - - public bool CookieExists(string key) - { - return !_config.DisableHttpContextItemsUseInCookieOperations && - _httpContextAccessor.HttpContext.Items.ContainsKey($"personalisationGroups.cookie.{key}") - || _httpContextAccessor.HttpContext.Request.Cookies[key] != null; - } - - public string GetCookieValue(string key) - { - if (!_config.DisableHttpContextItemsUseInCookieOperations && - _httpContextAccessor.HttpContext.Items.ContainsKey($"personalisationGroups.cookie.{key}")) - { - return _httpContextAccessor.HttpContext.Items[$"personalisationGroups.cookie.{key}"].ToString(); - } - - return _httpContextAccessor.HttpContext.Request.Cookies[key]; - } - - public void SetCookie(string key, string value, System.DateTime? expires = null, bool httpOnly = true) - { - if (AreCookiesDeclined()) - { - return; - } - - if (!_config.DisableHttpContextItemsUseInCookieOperations) - { - _httpContextAccessor.HttpContext.Items[$"personalisationGroups.cookie.{key}"] = value; - } - - var cookieOptions = new CookieOptions - { - Expires = expires.HasValue ? new DateTimeOffset(expires.Value) : (DateTimeOffset?)null, - HttpOnly = httpOnly, - Secure = true - }; - _httpContextAccessor.HttpContext.Response.Cookies.Append(key, value, cookieOptions); - } - - public void DeleteCookie(string key) => _httpContextAccessor.HttpContext.Response.Cookies.Delete(key); - - private bool AreCookiesDeclined() - { - // Cookies can be declined by a solution developer either by setting a cookie or session variable. - // If either of these exist, we shouldn't write any cookies. - return _httpContextAccessor.HttpContext.Request.Cookies[_config.CookieKeyForTrackingCookiesDeclined] != null || - _sessionManager.GetSessionValue(_config.SessionKeyForTrackingCookiesDeclined) != null; - } - } -} diff --git a/PersonalisationGroups.Core/Providers/Cookie/ICookieProvider.cs b/PersonalisationGroups.Core/Providers/Cookie/ICookieProvider.cs deleted file mode 100644 index de94b0c..0000000 --- a/PersonalisationGroups.Core/Providers/Cookie/ICookieProvider.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Cookie -{ - public interface ICookieProvider - { - bool CookieExists(string key); - - string GetCookieValue(string key); - - void SetCookie(string key, string value, System.DateTime? expires = null, bool httpOnly = true); - - void DeleteCookie(string key); - } -} diff --git a/PersonalisationGroups.Core/Providers/DateTime/DateTimeProvider.cs b/PersonalisationGroups.Core/Providers/DateTime/DateTimeProvider.cs deleted file mode 100644 index 892967c..0000000 --- a/PersonalisationGroups.Core/Providers/DateTime/DateTimeProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.DateTime -{ - public class DateTimeProvider : IDateTimeProvider - { - public System.DateTime GetCurrentDateTime() => System.DateTime.Now; - } -} diff --git a/PersonalisationGroups.Core/Providers/DateTime/IDateTimeProvider.cs b/PersonalisationGroups.Core/Providers/DateTime/IDateTimeProvider.cs deleted file mode 100644 index a0f3049..0000000 --- a/PersonalisationGroups.Core/Providers/DateTime/IDateTimeProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.DateTime -{ - public interface IDateTimeProvider - { - System.DateTime GetCurrentDateTime(); - } -} diff --git a/PersonalisationGroups.Core/Providers/GeoLocation/CdnHeaderCountryCodeProvider.cs b/PersonalisationGroups.Core/Providers/GeoLocation/CdnHeaderCountryCodeProvider.cs deleted file mode 100644 index 81f9cd1..0000000 --- a/PersonalisationGroups.Core/Providers/GeoLocation/CdnHeaderCountryCodeProvider.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Providers.RequestHeaders; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation -{ - public class CdnHeaderCountryCodeProvider : ICountryCodeProvider - { - private readonly PersonalisationGroupsConfig _config; - private readonly IRequestHeadersProvider _requestHeadersProvider; - - public CdnHeaderCountryCodeProvider(IOptions config, IRequestHeadersProvider requestHeadersProvider) - { - _config = config.Value; - _requestHeadersProvider = requestHeadersProvider; - } - - public string GetCountryCode() - { - var headers = _requestHeadersProvider.GetHeaders(); - var headerName = _config.CdnCountryCodeHttpHeaderName; - return headers?[headerName] ?? string.Empty; - } - } -} diff --git a/PersonalisationGroups.Core/Providers/GeoLocation/Continent.cs b/PersonalisationGroups.Core/Providers/GeoLocation/Continent.cs deleted file mode 100644 index 39b909a..0000000 --- a/PersonalisationGroups.Core/Providers/GeoLocation/Continent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation -{ - public class Continent - { - public string Code { get; set; } - - public string Name { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Providers/GeoLocation/Country.cs b/PersonalisationGroups.Core/Providers/GeoLocation/Country.cs deleted file mode 100644 index da4051a..0000000 --- a/PersonalisationGroups.Core/Providers/GeoLocation/Country.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation -{ - public class Country - { - public string Code { get; set; } - - public string Name { get; set; } - } -} diff --git a/PersonalisationGroups.Core/Providers/GeoLocation/CountryCodeProvider.cs b/PersonalisationGroups.Core/Providers/GeoLocation/CountryCodeProvider.cs deleted file mode 100644 index d5be47b..0000000 --- a/PersonalisationGroups.Core/Providers/GeoLocation/CountryCodeProvider.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation -{ - public enum CountryCodeProvider - { - MaxMindDatabase, - CdnHeader - } -} diff --git a/PersonalisationGroups.Core/Providers/GeoLocation/ICountryCodeProvider.cs b/PersonalisationGroups.Core/Providers/GeoLocation/ICountryCodeProvider.cs deleted file mode 100644 index 06182d8..0000000 --- a/PersonalisationGroups.Core/Providers/GeoLocation/ICountryCodeProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation -{ - public interface ICountryCodeProvider - { - string GetCountryCode(); - } -} diff --git a/PersonalisationGroups.Core/Providers/GeoLocation/IGeoLocationProvider.cs b/PersonalisationGroups.Core/Providers/GeoLocation/IGeoLocationProvider.cs deleted file mode 100644 index 3fc88a4..0000000 --- a/PersonalisationGroups.Core/Providers/GeoLocation/IGeoLocationProvider.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation -{ - public interface IGeoLocationProvider - { - Continent GetContinentFromIp(string ip); - - Country GetCountryFromIp(string ip); - - Region GetRegionFromIp(string ip); - } -} diff --git a/PersonalisationGroups.Core/Providers/GeoLocation/MaxMindCountryCodeFromIpProvider.cs b/PersonalisationGroups.Core/Providers/GeoLocation/MaxMindCountryCodeFromIpProvider.cs deleted file mode 100644 index c1244a4..0000000 --- a/PersonalisationGroups.Core/Providers/GeoLocation/MaxMindCountryCodeFromIpProvider.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Our.Umbraco.PersonalisationGroups.Core.Providers.Ip; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation -{ - public class MaxMindCountryCodeFromIpProvider : ICountryCodeProvider - { - private readonly IIpProvider _ipProvider; - private readonly IGeoLocationProvider _geoLocationProvider; - - public MaxMindCountryCodeFromIpProvider(IIpProvider ipProvider, IGeoLocationProvider geoLocationProvider) - { - _ipProvider = ipProvider; - _geoLocationProvider = geoLocationProvider; - } - - public string GetCountryCode() - { - var ip = _ipProvider.GetIp(); - if (string.IsNullOrEmpty(ip)) - { - return string.Empty; - } - - var country = _geoLocationProvider.GetCountryFromIp(ip); - return country != null ? country.Code : string.Empty; - } - } -} diff --git a/PersonalisationGroups.Core/Providers/GeoLocation/MaxMindGeoLocationProvider.cs b/PersonalisationGroups.Core/Providers/GeoLocation/MaxMindGeoLocationProvider.cs deleted file mode 100644 index b1a49c6..0000000 --- a/PersonalisationGroups.Core/Providers/GeoLocation/MaxMindGeoLocationProvider.cs +++ /dev/null @@ -1,170 +0,0 @@ -using MaxMind.GeoIP2; -using MaxMind.GeoIP2.Exceptions; -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using System.IO; -using System.Linq; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Core.Hosting; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation -{ - public class MaxMindGeoLocationProvider : IGeoLocationProvider - { - private readonly string _pathToCountryDb; - private readonly string _pathToCityDb; - private readonly AppCaches _appCaches; - - public MaxMindGeoLocationProvider(IOptions config, IHostingEnvironment hostingEnvironment, AppCaches appCaches) - { - _pathToCountryDb = hostingEnvironment.MapPathContentRoot(config.Value.GeoLocationCountryDatabasePath); - _pathToCityDb = hostingEnvironment.MapPathContentRoot(config.Value.GeoLocationCityDatabasePath); - _appCaches = appCaches; - } - - public Continent GetContinentFromIp(string ip) - { - var cacheKey = $"PersonalisationGroups_GeoLocation_Continent_{ip}"; - var cachedItem = _appCaches.RuntimeCache.Get(cacheKey, - () => - { - try - { - using (var reader = new DatabaseReader(_pathToCountryDb)) - { - try - { - var response = reader.Country(ip); - return new Continent { Code = response.Continent.Code, Name = response.Continent.Name, }; - } - catch (AddressNotFoundException) - { - return null; - } - catch (GeoIP2Exception ex) - { - if (IsInvalidIpException(ex)) - { - return null; - } - - throw; - } - } - } - catch (FileNotFoundException) - { - throw new FileNotFoundException( - $"MaxMind Geolocation database required for locating visitor continent from IP address not found, expected at: {_pathToCountryDb}. The path is derived from either the default ({AppConstants.DefaultGeoLocationCountryDatabasePath}) or can be configured using a relative path in an appSetting with key: \"{nameof(PersonalisationGroupsConfig.GeoLocationCountryDatabasePath)}\"", - _pathToCountryDb); - } - }); - - return cachedItem as Continent; - } - - public Country GetCountryFromIp(string ip) - { - var cacheKey = $"PersonalisationGroups_GeoLocation_Country_{ip}"; - var cachedItem = _appCaches.RuntimeCache.Get(cacheKey, - () => - { - try - { - using (var reader = new DatabaseReader(_pathToCountryDb)) - { - try - { - var response = reader.Country(ip); - return new Country { Code = response.Country.IsoCode, Name = response.Country.Name, }; - } - catch (AddressNotFoundException) - { - return null; - } - catch (GeoIP2Exception ex) - { - if (IsInvalidIpException(ex)) - { - return null; - } - - throw; - } - } - } - catch (FileNotFoundException) - { - throw new FileNotFoundException( - $"MaxMind Geolocation database required for locating visitor country from IP address not found, expected at: {_pathToCountryDb}. The path is derived from either the default ({AppConstants.DefaultGeoLocationCountryDatabasePath}) or can be configured using a relative path in an appSetting with key: \"{nameof(PersonalisationGroupsConfig.GeoLocationCountryDatabasePath)}\"", - _pathToCountryDb); - } - }); - - return cachedItem as Country; - } - - public Region GetRegionFromIp(string ip) - { - var cacheKey = $"PersonalisationGroups_GeoLocation_Region_{ip}"; - var cachedItem = _appCaches.RuntimeCache.Get(cacheKey, - () => - { - try - { - using (var reader = new DatabaseReader(_pathToCityDb)) - { - try - { - var response = reader.City(ip); - var region = new Region - { - City = response.City.Name, - Subdivisions = response.Subdivisions - .Select(x => x.Name) - .Union(response.Subdivisions - .SelectMany(x => x.Names - .Where(y => !string.IsNullOrEmpty(y.Value)) - .Select(y => y.Value))) - .ToArray(), - Country = new Country - { - Code = response.Country.IsoCode, - Name = response.Country.Name, - } - }; - - return region; - } - catch (AddressNotFoundException) - { - return null; - } - catch (GeoIP2Exception ex) - { - if (IsInvalidIpException(ex)) - { - return null; - } - - throw; - } - } - } - catch (FileNotFoundException) - { - throw new FileNotFoundException( - $"MaxMind Geolocation database required for locating visitor region from IP address not found, expected at: {_pathToCountryDb}. The path is derived from either the default ({AppConstants.DefaultGeoLocationCountryDatabasePath}) or can be configured using a relative path in an appSetting with key: \"{nameof(PersonalisationGroupsConfig.GeoLocationCityDatabasePath)}\"", - _pathToCountryDb); - } - }); - - return cachedItem as Region; - } - - private static bool IsInvalidIpException(GeoIP2Exception ex) - { - return ex.Message.StartsWith("The specified IP address was incorrectly formatted"); - } - } -} diff --git a/PersonalisationGroups.Core/Providers/GeoLocation/Region.cs b/PersonalisationGroups.Core/Providers/GeoLocation/Region.cs deleted file mode 100644 index 7bd9db2..0000000 --- a/PersonalisationGroups.Core/Providers/GeoLocation/Region.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation -{ - using System.Collections.Generic; - - public class Region - { - public string City { get; set; } - - public string[] Subdivisions { get; set; } - - public Country Country { get; set; } - - public string[] GetAllNames() - { - var names = new List { City }; - if (Subdivisions != null) - { - names.AddRange(Subdivisions); - } - - return names.ToArray(); - } - } -} diff --git a/PersonalisationGroups.Core/Providers/Host/HttpContextHostProvider.cs b/PersonalisationGroups.Core/Providers/Host/HttpContextHostProvider.cs deleted file mode 100644 index 28984b0..0000000 --- a/PersonalisationGroups.Core/Providers/Host/HttpContextHostProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Host -{ - public class HttpContextHostProvider : IHostProvider - { - private readonly IHttpContextAccessor _httpContextAccessor; - - public HttpContextHostProvider(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - public string GetHost() - { - return _httpContextAccessor.HttpContext.Request.Host.Value; - } - } -} diff --git a/PersonalisationGroups.Core/Providers/Host/IHostProvider.cs b/PersonalisationGroups.Core/Providers/Host/IHostProvider.cs deleted file mode 100644 index e476def..0000000 --- a/PersonalisationGroups.Core/Providers/Host/IHostProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Host -{ - public interface IHostProvider - { - string GetHost(); - } -} diff --git a/PersonalisationGroups.Core/Providers/Ip/ClientIpParser.cs b/PersonalisationGroups.Core/Providers/Ip/ClientIpParser.cs deleted file mode 100644 index 952eb8c..0000000 --- a/PersonalisationGroups.Core/Providers/Ip/ClientIpParser.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Ip -{ - public class ClientIpParser - { - private static readonly IEnumerable HeaderKeys = new[] - { - "CF-Connecting-IP", "HTTP_X_FORWARDED_FOR", "REMOTE_ADDR", - "HTTP_CLIENT_IP", "HTTP_X_FORWARDED", "HTTP_X_CLUSTER_CLIENT_IP", - "HTTP_FORWARDED_FOR", "HTTP_FORWARDED" - }; - - public string ParseClientIp(HttpContext httpContext) - { - if (httpContext == null) - { - return string.Empty; - } - - foreach (var key in HeaderKeys) - { - if (TryParseIpFromHeaders(httpContext.Request.Headers, key, out string ip)) - { - return ip; - } - } - - return httpContext.Connection.RemoteIpAddress.ToString(); - } - - private bool TryParseIpFromHeaders(IHeaderDictionary requestHeaders, string key, out string ip) - { - var value = requestHeaders[key]; - if (value == StringValues.Empty) - { - ip = string.Empty; - return false; - } - - // We don't want local ips - if (value.ToString().StartsWith("192.")) - { - ip = string.Empty; - return false; - } - - // We might not have a single IP here, as it's possible if the request has passed through multiple proxies, there will be - // additional ones in the header - // If so, the original requesting IP is the first one in a comma+space delimited list - value = value.ToString().Split(new[] { ", " }, StringSplitOptions.None).First(); - ip = RemovePortNumberFromIp(value); - - // Finally, ensure we have a valid IP. - return IsValidIp(ip); - } - - private static string RemovePortNumberFromIp(string ip) - { - if (ip.Contains(":")) - { - ip = ip.Substring(0, ip.IndexOf(":", StringComparison.Ordinal)); - } - - return ip; - } - - private bool IsValidIp(string ip) => !string.IsNullOrEmpty(ip) && IPAddress.TryParse(ip, out _); - } -} diff --git a/PersonalisationGroups.Core/Providers/Ip/HttpContextIpProvider.cs b/PersonalisationGroups.Core/Providers/Ip/HttpContextIpProvider.cs deleted file mode 100644 index 70d3f00..0000000 --- a/PersonalisationGroups.Core/Providers/Ip/HttpContextIpProvider.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Ip -{ - public class HttpContextIpProvider : IIpProvider - { - private readonly PersonalisationGroupsConfig _config; - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly ClientIpParser _clientIpParser; - - public HttpContextIpProvider(IOptions config, IHttpContextAccessor httpContextAccessor, ClientIpParser clientIpParser) - { - _config = config.Value; - _httpContextAccessor = httpContextAccessor; - _clientIpParser = clientIpParser; - } - - public string GetIp() - { - var ip = GetIpFromHttpContext(); - if (ip == "::1") - { - ip = "127.0.0.1"; - } - - return ip; - } - - private string GetIpFromHttpContext() - { - // Return a test Ip if we've configured one. - var testIp = _config.TestFixedIp; - if (!string.IsNullOrEmpty(testIp)) - { - return testIp; - } - - // Otherwise retrieve from the HTTP context. - return _clientIpParser.ParseClientIp(_httpContextAccessor.HttpContext); - } - } -} diff --git a/PersonalisationGroups.Core/Providers/Ip/IIpProvider.cs b/PersonalisationGroups.Core/Providers/Ip/IIpProvider.cs deleted file mode 100644 index 417dc30..0000000 --- a/PersonalisationGroups.Core/Providers/Ip/IIpProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Ip -{ - public interface IIpProvider - { - string GetIp(); - } -} diff --git a/PersonalisationGroups.Core/Providers/MemberGroup/IMemberGroupProvider.cs b/PersonalisationGroups.Core/Providers/MemberGroup/IMemberGroupProvider.cs deleted file mode 100644 index 4d71f50..0000000 --- a/PersonalisationGroups.Core/Providers/MemberGroup/IMemberGroupProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.MemberGroup -{ - using System.Collections.Generic; - - public interface IMemberGroupProvider - { - IEnumerable GetMemberGroups(); - } -} diff --git a/PersonalisationGroups.Core/Providers/MemberGroup/UmbracoMemberGroupProvider.cs b/PersonalisationGroups.Core/Providers/MemberGroup/UmbracoMemberGroupProvider.cs deleted file mode 100644 index 05359e9..0000000 --- a/PersonalisationGroups.Core/Providers/MemberGroup/UmbracoMemberGroupProvider.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Http; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Services; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.MemberGroup -{ - public class UmbracoMemberGroupProvider : IMemberGroupProvider - { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IMemberService _memberService; - - public UmbracoMemberGroupProvider(IHttpContextAccessor httpContextAccessor, IMemberService memberService) - { - _httpContextAccessor = httpContextAccessor; - _memberService = memberService; - } - - public IEnumerable GetMemberGroups() - { - return _httpContextAccessor.HttpContext.User.Identity.IsAuthenticated - ? GetAuthenticatedMemberGroups() - : Enumerable.Empty(); - } - - private IEnumerable GetAuthenticatedMemberGroups() - { - var memberGroups = _memberService.GetAllRoles(_httpContextAccessor.HttpContext.User.Identity.Name); - return memberGroups ?? Enumerable.Empty(); - } - } -} diff --git a/PersonalisationGroups.Core/Providers/MemberProfileField/IMemberProfileFieldProvider.cs b/PersonalisationGroups.Core/Providers/MemberProfileField/IMemberProfileFieldProvider.cs deleted file mode 100644 index fda792e..0000000 --- a/PersonalisationGroups.Core/Providers/MemberProfileField/IMemberProfileFieldProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.MemberProfileField -{ - public interface IMemberProfileFieldProvider - { - string GetMemberProfileFieldValue(string alias); - } -} diff --git a/PersonalisationGroups.Core/Providers/MemberProfileField/UmbracoMemberProfileFieldProvider.cs b/PersonalisationGroups.Core/Providers/MemberProfileField/UmbracoMemberProfileFieldProvider.cs deleted file mode 100644 index 8d5b936..0000000 --- a/PersonalisationGroups.Core/Providers/MemberProfileField/UmbracoMemberProfileFieldProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Umbraco.Cms.Core.Services; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.MemberProfileField -{ - public class UmbracoMemberProfileFieldProvider : IMemberProfileFieldProvider - { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IMemberService _memberService; - - public UmbracoMemberProfileFieldProvider(IHttpContextAccessor httpContextAccessor, IMemberService memberService) - { - _httpContextAccessor = httpContextAccessor; - _memberService = memberService; - } - - public string GetMemberProfileFieldValue(string alias) - { - return _httpContextAccessor.HttpContext.User.Identity.IsAuthenticated - ? GetAuthenticatedMemberProfileFieldValue(alias) - : string.Empty; - } - - private string GetAuthenticatedMemberProfileFieldValue(string alias) - { - var member = _memberService.GetByUsername(_httpContextAccessor.HttpContext.User.Identity.Name); - if (member == null) - { - return string.Empty; - } - - return member.GetValue(alias)?.ToString() ?? string.Empty; - } - } -} diff --git a/PersonalisationGroups.Core/Providers/MemberType/IMemberTypeProvider.cs b/PersonalisationGroups.Core/Providers/MemberType/IMemberTypeProvider.cs deleted file mode 100644 index 499e276..0000000 --- a/PersonalisationGroups.Core/Providers/MemberType/IMemberTypeProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.MemberType -{ - public interface IMemberTypeProvider - { - string GetMemberType(); - } -} diff --git a/PersonalisationGroups.Core/Providers/MemberType/UmbracoMemberTypeProvider.cs b/PersonalisationGroups.Core/Providers/MemberType/UmbracoMemberTypeProvider.cs deleted file mode 100644 index 24e04f2..0000000 --- a/PersonalisationGroups.Core/Providers/MemberType/UmbracoMemberTypeProvider.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Umbraco.Cms.Core.Services; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.MemberType -{ - public class UmbracoMemberTypeProvider : IMemberTypeProvider - { - private readonly IHttpContextAccessor _httpContextAccessor; - private readonly IMemberService _memberService; - - public UmbracoMemberTypeProvider(IHttpContextAccessor httpContextAccessor, IMemberService memberService) - { - _httpContextAccessor = httpContextAccessor; - _memberService = memberService; - } - - public string GetMemberType() - { - return _httpContextAccessor.HttpContext.User.Identity.IsAuthenticated - ? GetAuthenticatedMemberType() - : string.Empty; - } - - private string GetAuthenticatedMemberType() - { - var member = _memberService.GetByUsername(_httpContextAccessor.HttpContext.User.Identity.Name); - if (member == null) - { - return string.Empty; - } - - return member.ContentType.Alias; - } - } -} diff --git a/PersonalisationGroups.Core/Providers/NumberOfVisits/CookieNumberOfVisitsProvider.cs b/PersonalisationGroups.Core/Providers/NumberOfVisits/CookieNumberOfVisitsProvider.cs deleted file mode 100644 index 5cc819a..0000000 --- a/PersonalisationGroups.Core/Providers/NumberOfVisits/CookieNumberOfVisitsProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Cookie; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.NumberOfVisits -{ - public class CookieNumberOfVisitsProvider : INumberOfVisitsProvider - { - private readonly PersonalisationGroupsConfig _config; - private readonly ICookieProvider _cookieProvider; - - public CookieNumberOfVisitsProvider(IOptions config, ICookieProvider cookieProvider) - { - _config = config.Value; - _cookieProvider = cookieProvider; - } - - public int GetNumberOfVisits() - { - var cookieValue = _cookieProvider.GetCookieValue(_config.CookieKeyForTrackingNumberOfVisits); - - if (!string.IsNullOrEmpty(cookieValue) && int.TryParse(cookieValue, out int cookieNumericValue)) - { - return cookieNumericValue; - } - - return 0; - } - } -} diff --git a/PersonalisationGroups.Core/Providers/NumberOfVisits/INumberOfVisitsProvider.cs b/PersonalisationGroups.Core/Providers/NumberOfVisits/INumberOfVisitsProvider.cs deleted file mode 100644 index 3557506..0000000 --- a/PersonalisationGroups.Core/Providers/NumberOfVisits/INumberOfVisitsProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.NumberOfVisits -{ - public interface INumberOfVisitsProvider - { - int GetNumberOfVisits(); - } -} diff --git a/PersonalisationGroups.Core/Providers/PagesViewed/CookiePagesViewedProvider.cs b/PersonalisationGroups.Core/Providers/PagesViewed/CookiePagesViewedProvider.cs deleted file mode 100644 index 1bbf704..0000000 --- a/PersonalisationGroups.Core/Providers/PagesViewed/CookiePagesViewedProvider.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Cookie; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.PagesViewed -{ - public class CookiePagesViewedProvider : IPagesViewedProvider - { - private readonly PersonalisationGroupsConfig _config; - private readonly ICookieProvider _cookieProvider; - - public CookiePagesViewedProvider(IOptions config, ICookieProvider cookieProvider) - { - _config = config.Value; - _cookieProvider = cookieProvider; - } - - public IEnumerable GetNodeIdsViewed() - { - var cookieValue = _cookieProvider.GetCookieValue(_config.CookieKeyForTrackingPagesViewed); - - if (!string.IsNullOrEmpty(cookieValue)) - { - return cookieValue.ParsePageIds(); - } - - return Enumerable.Empty(); - } - } -} diff --git a/PersonalisationGroups.Core/Providers/PagesViewed/IPagesViewedProvider.cs b/PersonalisationGroups.Core/Providers/PagesViewed/IPagesViewedProvider.cs deleted file mode 100644 index ce1f565..0000000 --- a/PersonalisationGroups.Core/Providers/PagesViewed/IPagesViewedProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.PagesViewed -{ - using System.Collections.Generic; - - public interface IPagesViewedProvider - { - IEnumerable GetNodeIdsViewed(); - } -} diff --git a/PersonalisationGroups.Core/Providers/PagesViewed/StringExtensions.cs b/PersonalisationGroups.Core/Providers/PagesViewed/StringExtensions.cs deleted file mode 100644 index 86f727a..0000000 --- a/PersonalisationGroups.Core/Providers/PagesViewed/StringExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.PagesViewed -{ - public static class StringExtensions - { - public static List ParsePageIds(this string commaSeparatedPageIds) - { - return commaSeparatedPageIds - .Split(',') - .Aggregate(new List(), - (result, value) => - { - if (int.TryParse(value, out var item)) - { - result.Add(item); - } - - return result; - }); - } - } -} diff --git a/PersonalisationGroups.Core/Providers/Querystring/HttpContextQuerystringProvider.cs b/PersonalisationGroups.Core/Providers/Querystring/HttpContextQuerystringProvider.cs deleted file mode 100644 index d601fd4..0000000 --- a/PersonalisationGroups.Core/Providers/Querystring/HttpContextQuerystringProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Querystring -{ - public class HttpContextQuerystringProvider : IQuerystringProvider - { - private readonly IHttpContextAccessor _httpContextAccessor; - - public HttpContextQuerystringProvider(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - public IQueryCollection GetQuerystring() - { - return _httpContextAccessor.HttpContext.Request.Query; - } - } -} diff --git a/PersonalisationGroups.Core/Providers/Querystring/IQuerystringProvider.cs b/PersonalisationGroups.Core/Providers/Querystring/IQuerystringProvider.cs deleted file mode 100644 index 4a2a9a7..0000000 --- a/PersonalisationGroups.Core/Providers/Querystring/IQuerystringProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Querystring -{ - public interface IQuerystringProvider - { - IQueryCollection GetQuerystring(); - } -} diff --git a/PersonalisationGroups.Core/Providers/Referrer/HttpContextReferrerProvider.cs b/PersonalisationGroups.Core/Providers/Referrer/HttpContextReferrerProvider.cs deleted file mode 100644 index 96a651f..0000000 --- a/PersonalisationGroups.Core/Providers/Referrer/HttpContextReferrerProvider.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Referrer -{ - public class HttpContextReferrerProvider : IReferrerProvider - { - private readonly IHttpContextAccessor _httpContextAccessor; - - public HttpContextReferrerProvider(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - public string GetReferrer() => _httpContextAccessor.HttpContext.Request.GetTypedHeaders().Referer?.AbsoluteUri ?? string.Empty; - } -} diff --git a/PersonalisationGroups.Core/Providers/Referrer/IReferrerProvider.cs b/PersonalisationGroups.Core/Providers/Referrer/IReferrerProvider.cs deleted file mode 100644 index f33dcc4..0000000 --- a/PersonalisationGroups.Core/Providers/Referrer/IReferrerProvider.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Referrer -{ - public interface IReferrerProvider - { - string GetReferrer(); - } -} diff --git a/PersonalisationGroups.Core/Providers/RequestHeaders/HttpContextRequestHeadersProvider.cs b/PersonalisationGroups.Core/Providers/RequestHeaders/HttpContextRequestHeadersProvider.cs deleted file mode 100644 index 91b8d3e..0000000 --- a/PersonalisationGroups.Core/Providers/RequestHeaders/HttpContextRequestHeadersProvider.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.RequestHeaders -{ - public class HttpContextRequestHeadersProvider : IRequestHeadersProvider - { - private readonly IHttpContextAccessor _httpContextAccessor; - - public HttpContextRequestHeadersProvider(IHttpContextAccessor httpContextAccessor) - { - _httpContextAccessor = httpContextAccessor; - } - - public IHeaderDictionary GetHeaders() - { - return _httpContextAccessor.HttpContext.Request.Headers; - } - } -} diff --git a/PersonalisationGroups.Core/Providers/RequestHeaders/IRequestHeadersProvider.cs b/PersonalisationGroups.Core/Providers/RequestHeaders/IRequestHeadersProvider.cs deleted file mode 100644 index 7c5ae2b..0000000 --- a/PersonalisationGroups.Core/Providers/RequestHeaders/IRequestHeadersProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.AspNetCore.Http; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.RequestHeaders -{ - public interface IRequestHeadersProvider - { - IHeaderDictionary GetHeaders(); - } -} diff --git a/PersonalisationGroups.Core/Providers/Session/HttpContextSessionProvider.cs b/PersonalisationGroups.Core/Providers/Session/HttpContextSessionProvider.cs deleted file mode 100644 index cbd6c9d..0000000 --- a/PersonalisationGroups.Core/Providers/Session/HttpContextSessionProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Umbraco.Cms.Core.Web; - -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Session -{ - public class HttpContextSessionProvider : ISessionProvider - { - private readonly ISessionManager _sessionManager; - - public HttpContextSessionProvider(ISessionManager sessionManager) - { - _sessionManager = sessionManager; - } - - public bool KeyExists(string key) => _sessionManager.GetSessionValue(key) != null; - - public string GetValue(string key) => _sessionManager.GetSessionValue(key)?.ToString() ?? string.Empty; - } -} diff --git a/PersonalisationGroups.Core/Providers/Session/ISessionProvider.cs b/PersonalisationGroups.Core/Providers/Session/ISessionProvider.cs deleted file mode 100644 index 360a677..0000000 --- a/PersonalisationGroups.Core/Providers/Session/ISessionProvider.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Providers.Session -{ - public interface ISessionProvider - { - bool KeyExists(string key); - - string GetValue(string key); - } -} diff --git a/PersonalisationGroups.Core/Services/CriteriaService.cs b/PersonalisationGroups.Core/Services/CriteriaService.cs deleted file mode 100644 index ecee170..0000000 --- a/PersonalisationGroups.Core/Services/CriteriaService.cs +++ /dev/null @@ -1,82 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Criteria; -using Our.Umbraco.PersonalisationGroups.Core.Extensions; -using System; -using System.Collections.Generic; -using System.Linq; -using Umbraco.Cms.Core.Cache; - -namespace Our.Umbraco.PersonalisationGroups.Core.Services -{ - public class CriteriaService : ICriteriaService - { - private readonly PersonalisationGroupsConfig _config; - private readonly IServiceProvider _serviceProvider; - private readonly AppCaches _appCaches; - - public CriteriaService(IOptions config, IServiceProvider serviceProvider, AppCaches appCaches) - { - _config = config.Value; - _serviceProvider = serviceProvider; - _appCaches = appCaches; - } - - public IEnumerable GetAvailableCriteria() - { - var cacheKey = $"PersonalisationGroups_Criteria"; - var criteriaDictionary = _appCaches.RuntimeCache.Get(cacheKey, - () => BuildAvailableCriteria()) as Dictionary; - - var criteria = criteriaDictionary.Values.Select(x => x); - - if (!string.IsNullOrEmpty(_config.IncludeCriteria)) - { - criteria = criteria - .Where(x => _config.IncludeCriteria.Split(',').Contains(x. Alias, StringComparer.InvariantCultureIgnoreCase)); - } - - if (!string.IsNullOrEmpty(_config.ExcludeCriteria)) - { - criteria = criteria - .Where(x => !_config.ExcludeCriteria.Split(',').Contains(x.Alias, StringComparer.InvariantCultureIgnoreCase)); - } - - return criteria.OrderBy(x => x.Name); - } - - /// - /// Helper to scan the loaded assemblies and retrieve the available personalisation group criteria (that implement the - /// interface - /// - private Dictionary BuildAvailableCriteria() - { - var lockObject = new object(); - lock (lockObject) - { - var criteria = new Dictionary(); - var type = typeof(IPersonalisationGroupCriteria); - var typesImplementingInterface = AppDomain.CurrentDomain.GetAssemblies() - .SelectMany(s => s.GetLoadableTypes()) - .Where(p => type.IsAssignableFrom(p) && p.IsClass) - .Select(x => ActivatorUtilities.CreateInstance(_serviceProvider, x) as IPersonalisationGroupCriteria) - .Where(x => x != null); - - foreach (var typeImplementingInterface in typesImplementingInterface) - { - // Aliases have to be unique - but in case they aren't, make sure we don't attempt - // to load a second criteria of the same alias. Issue #14. - if (criteria.ContainsKey(typeImplementingInterface.Alias)) - { - continue; - } - - criteria.Add(typeImplementingInterface.Alias, typeImplementingInterface); - } - - return criteria; - } - } - } -} diff --git a/PersonalisationGroups.Core/Services/GroupMatchingService.cs b/PersonalisationGroups.Core/Services/GroupMatchingService.cs deleted file mode 100644 index 8639211..0000000 --- a/PersonalisationGroups.Core/Services/GroupMatchingService.cs +++ /dev/null @@ -1,236 +0,0 @@ -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Criteria; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Extensions; - -namespace Our.Umbraco.PersonalisationGroups.Core.Services -{ - public class GroupMatchingService : IGroupMatchingService - { - private readonly PersonalisationGroupsConfig _config; - private readonly ICriteriaService _criteriaService; - private readonly IStickyMatchService _stickyMatchService; - private readonly IPublishedValueFallback _publishedValueFallback; - - public GroupMatchingService( - IOptions config, - ICriteriaService criteriaService, - IStickyMatchService stickyMatchService, - IPublishedValueFallback publishedValueFallback) - { - _config = config.Value; - _criteriaService = criteriaService; - _stickyMatchService = stickyMatchService; - _publishedValueFallback = publishedValueFallback; - } - - /// - /// Gets the list of personalisation group content items associated with the current content item - /// - /// Instance of IPublished content - /// List of personalisation group content items - public IList GetPickedGroups(IPublishedElement content) - { - var propertyAlias = _config.GroupPickerAlias; - if (content.HasProperty(propertyAlias)) - { - var rawValue = content.Value(propertyAlias); - switch (rawValue) - { - case IEnumerable list: - return list.ToList(); - case IPublishedContent group: - return new List { group }; - } - } - - return new List(); - } - - public bool MatchGroup(IPublishedContent pickedGroup) => MatchGroups(new List { pickedGroup }); - - public bool MatchGroups(IList pickedGroups) - { - // Package is disabled, return default. - if (_config.DisablePackage) - { - return true; - } - - // Check each personalisation group assigned for a match with the current site visitor. - foreach (var group in pickedGroups) - { - var definition = group.Value(_publishedValueFallback, AppConstants.PersonalisationGroupDefinitionPropertyAlias); - if (_stickyMatchService.IsStickyMatch(definition, group.Id)) - { - return true; - } - - var matchCount = CountMatchingDefinitionDetails(definition); - - // If matching any and matched at least one, or matching all and matched all - we've matched one of the definitions - // associated with a selected personalisation group. - if (definition.Match == PersonalisationGroupDefinitionMatch.Any && matchCount > 0 || - definition.Match == PersonalisationGroupDefinitionMatch.All && matchCount == definition.Details.Count()) - { - _stickyMatchService.MakeStickyMatch(definition, group.Id); - return true; - } - } - - // If we've got here, we haven't found a match. - return false; - } - - /// - /// Gets a count of the number of the definition details for a given personalisation group definition that matches - /// the current site visitor. - /// - /// Personalisation group definition - /// Number of definition details that match - public int CountMatchingDefinitionDetails(PersonalisationGroupDefinition definition) - { - var matchCount = 0; - foreach (var detail in definition.Details) - { - var isMatch = IsMatch(detail); - if (isMatch) - { - matchCount++; - } - - // We can short-cut here if matching any and found one match, or matching all and found one mismatch - if ((isMatch && definition.Match == PersonalisationGroupDefinitionMatch.Any) || - (!isMatch && definition.Match == PersonalisationGroupDefinitionMatch.All)) - { - break; - } - } - - return matchCount; - } - - /// - /// Checks if a given detail record of a personalisation group definition matches the current site visitor. - /// - /// Personalisation group definition detail record - /// True of the current site visitor matches the definition - private bool IsMatch(PersonalisationGroupDefinitionDetail definitionDetail) - { - var criterium = _criteriaService.GetAvailableCriteria().SingleOrDefault(x => string.Equals(x.Alias, definitionDetail.Alias, StringComparison.InvariantCultureIgnoreCase)); - if (criterium == null) - { - throw new KeyNotFoundException($"Personalisation group criteria not found with alias '{definitionDetail.Alias}'"); - } - - return criterium.MatchesVisitor(definitionDetail.Definition); - } - - public bool MatchGroupsByName(string[] groupNames, IList groups, PersonalisationGroupDefinitionMatch matchType) - { - // Package is disabled, return default - if (_config.DisablePackage) - { - return true; - } - - var matches = 0; - foreach (var groupName in groupNames) - { - var group = groups - .FirstOrDefault(x => string.Equals(x.Name, groupName, StringComparison.InvariantCultureIgnoreCase)); - if (@group == null) - { - continue; - } - - if (MatchGroup(@group)) - { - if (matchType == PersonalisationGroupDefinitionMatch.Any) - { - return true; - } - - matches++; - } - else - { - if (matchType == PersonalisationGroupDefinitionMatch.All) - { - return false; - } - } - } - - return matches == groupNames.Length; - } - - public int ScoreGroup(IPublishedContent pickedGroup) => ScoreGroups(new List { pickedGroup }); - - public int ScoreGroups(IList pickedGroups) - { - // Package is disabled, return default - if (_config.DisablePackage) - { - return 0; - } - - // Check each personalisation group assigned for a match with the current site visitor - var score = 0; - foreach (var group in pickedGroups) - { - var definition = group.Value(_publishedValueFallback, AppConstants.PersonalisationGroupDefinitionPropertyAlias); - if (_stickyMatchService.IsStickyMatch(definition, group.Id)) - { - score += definition.Score; - continue; - } - - var matchCount = CountMatchingDefinitionDetails(definition); - - // If matching any and matched at least one, or matching all and matched all - we've matched one of the definitions - // associated with a selected personalisation group - if ((definition.Match == PersonalisationGroupDefinitionMatch.Any && matchCount > 0) || - (definition.Match == PersonalisationGroupDefinitionMatch.All && matchCount == definition.Details.Count())) - { - _stickyMatchService.MakeStickyMatch(definition, group.Id); - score += definition.Score; - } - } - - return score; - } - - public string CreatePersonalisationGroupsHashForVisitor(IPublishedContent personalisationGroupsRootNode) - { - var groups = personalisationGroupsRootNode.Descendants(AppConstants.DocumentTypeAliases.PersonalisationGroup); - var sb = new StringBuilder(); - foreach (var group in groups) - { - var definition = group.Value(AppConstants.PersonalisationGroupDefinitionPropertyAlias); - AppendMatchedGroupDetailToVisitorHashString(sb, definition, group.CreatorName()); - } - - return sb.ToString().GetHashCode().ToString(); - } - - private void AppendMatchedGroupDetailToVisitorHashString(StringBuilder sb, PersonalisationGroupDefinition definition, string name) - { - var matchCount = CountMatchingDefinitionDetails(definition); - var matched = (definition.Match == PersonalisationGroupDefinitionMatch.Any && matchCount > 0) || - (definition.Match == PersonalisationGroupDefinitionMatch.All && matchCount == definition.Details.Count()); - - if (sb.Length > 0) - { - sb.Append(','); - } - - sb.AppendFormat("{0}={1}", name, matched); - } - } -} diff --git a/PersonalisationGroups.Core/Services/ICriteriaService.cs b/PersonalisationGroups.Core/Services/ICriteriaService.cs deleted file mode 100644 index 801cbdc..0000000 --- a/PersonalisationGroups.Core/Services/ICriteriaService.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using Our.Umbraco.PersonalisationGroups.Core.Criteria; - -namespace Our.Umbraco.PersonalisationGroups.Core.Services -{ - public interface ICriteriaService - { - IEnumerable GetAvailableCriteria(); - } -} diff --git a/PersonalisationGroups.Core/Services/IGroupMatchingService.cs b/PersonalisationGroups.Core/Services/IGroupMatchingService.cs deleted file mode 100644 index 18d2c12..0000000 --- a/PersonalisationGroups.Core/Services/IGroupMatchingService.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Our.Umbraco.PersonalisationGroups.Core.Criteria; -using System.Collections.Generic; -using Umbraco.Cms.Core.Models.PublishedContent; - -namespace Our.Umbraco.PersonalisationGroups.Core.Services -{ - public interface IGroupMatchingService - { - IList GetPickedGroups(IPublishedElement content); - - bool MatchGroup(IPublishedContent pickedGroup); - - bool MatchGroups(IList pickedGroups); - - int CountMatchingDefinitionDetails(PersonalisationGroupDefinition definition); - - bool MatchGroupsByName(string[] groupNames, IList groups, PersonalisationGroupDefinitionMatch matchType); - - int ScoreGroup(IPublishedContent pickedGroup); - - int ScoreGroups(IList pickedGroups); - - string CreatePersonalisationGroupsHashForVisitor(IPublishedContent personalisationGroupsRootNode); - } -} diff --git a/PersonalisationGroups.Core/Services/IStickyMatchService.cs b/PersonalisationGroups.Core/Services/IStickyMatchService.cs deleted file mode 100644 index c1d2831..0000000 --- a/PersonalisationGroups.Core/Services/IStickyMatchService.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Our.Umbraco.PersonalisationGroups.Core.Criteria; - -namespace Our.Umbraco.PersonalisationGroups.Core.Services -{ - public interface IStickyMatchService - { - bool IsStickyMatch(PersonalisationGroupDefinition definition, int groupNodeId); - - void MakeStickyMatch(PersonalisationGroupDefinition definition, int groupNodeId); - } -} diff --git a/PersonalisationGroups.Core/Services/IUserActivityTracker.cs b/PersonalisationGroups.Core/Services/IUserActivityTracker.cs deleted file mode 100644 index ed30267..0000000 --- a/PersonalisationGroups.Core/Services/IUserActivityTracker.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Our.Umbraco.PersonalisationGroups.Core.Services -{ - public interface IUserActivityTracker - { - void TrackPageView(int pageId); - - void TrackSession(); - } -} \ No newline at end of file diff --git a/PersonalisationGroups.Core/Services/StickyMatchService.cs b/PersonalisationGroups.Core/Services/StickyMatchService.cs deleted file mode 100644 index 145fabf..0000000 --- a/PersonalisationGroups.Core/Services/StickyMatchService.cs +++ /dev/null @@ -1,117 +0,0 @@ -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Criteria; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Cookie; -using Our.Umbraco.PersonalisationGroups.Core.Providers.DateTime; -using System; -using System.Linq; - -namespace Our.Umbraco.PersonalisationGroups.Core.Services -{ - public class StickyMatchService : IStickyMatchService - { - private readonly PersonalisationGroupsConfig _config; - private readonly ICookieProvider _cookieProvider; - private readonly IDateTimeProvider _dateTimeProvider; - - public StickyMatchService(IOptions config, ICookieProvider cookieProvider, IDateTimeProvider dateTimeProvider) - { - _config = config.Value; - _cookieProvider = cookieProvider; - _dateTimeProvider = dateTimeProvider; - } - - public bool IsStickyMatch(PersonalisationGroupDefinition definition, int groupNodeId) - { - if (definition.Duration == PersonalisationGroupDefinitionDuration.Page) - { - return false; - } - - var key = GetCookieKeyForMatchedGroups(definition.Duration); - var cookieValue = _cookieProvider.GetCookieValue(key); - return !string.IsNullOrEmpty(cookieValue) && IsGroupMatched(cookieValue, groupNodeId); - } - - /// - /// Makes a matched group sticky for the visitor via a cookie setting according to group definition - /// - /// Matched group definition - /// Id of the matched groups node - public void MakeStickyMatch(PersonalisationGroupDefinition definition, int groupNodeId) - { - if (definition.Duration == PersonalisationGroupDefinitionDuration.Page) - { - return; - } - - var key = GetCookieKeyForMatchedGroups(definition.Duration); - var value = _cookieProvider.GetCookieValue(key); - if (!string.IsNullOrEmpty(value)) - { - value = AppendGroupNodeId(value, groupNodeId); - } - else - { - value = groupNodeId.ToString(); - } - - DateTime? expires = null; - if (definition.Duration == PersonalisationGroupDefinitionDuration.Visitor) - { - var cookieExpiryInDays = _config.PersistentMatchedGroupsCookieExpiryInDays; - expires = _dateTimeProvider.GetCurrentDateTime().AddDays(cookieExpiryInDays); - } - - _cookieProvider.SetCookie(key, value, expires); - } - - /// - /// Retrieves the cookie key to use for the matched groups - /// - /// Match group duration - /// Cookie key to use - private string GetCookieKeyForMatchedGroups(PersonalisationGroupDefinitionDuration duration) - { - switch (duration) - { - case PersonalisationGroupDefinitionDuration.Session: - return _config.CookieKeyForSessionMatchedGroups; - case PersonalisationGroupDefinitionDuration.Visitor: - return _config.CookieKeyForPersistentMatchedGroups; - default: - throw new InvalidOperationException("Only session and visitor personalisation groups can be tracked."); - } - } - - /// - /// Adds a matched group to the cookie for sticky groups - /// - /// Existing cookie value of matched group node Ids - /// Id of the matched groups node - /// Updated cookie value - private static string AppendGroupNodeId(string matchedGroupIds, int groupNodeId) - { - // Shouldn't exist as we don't try to append an already sticky group match, but just to be sure. - if (!IsGroupMatched(matchedGroupIds, groupNodeId)) - { - matchedGroupIds = matchedGroupIds + "," + groupNodeId; - } - - return matchedGroupIds; - } - - /// - /// Checks if group is matched in tracking cookie value - /// - /// Existing cookie value of matched group node Ids - /// Id of the matched groups node - /// True if matched - private static bool IsGroupMatched(string matchedGroupIds, int groupNodeId) - { - return matchedGroupIds - .Split(',') - .Any(x => int.Parse(x) == groupNodeId); - } - } -} diff --git a/PersonalisationGroups.Core/Services/UserActivityTracker.cs b/PersonalisationGroups.Core/Services/UserActivityTracker.cs deleted file mode 100644 index 27eef3e..0000000 --- a/PersonalisationGroups.Core/Services/UserActivityTracker.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Microsoft.Extensions.Options; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Cookie; -using Our.Umbraco.PersonalisationGroups.Core.Providers.DateTime; -using Our.Umbraco.PersonalisationGroups.Core.Providers.PagesViewed; - -namespace Our.Umbraco.PersonalisationGroups.Core.Services -{ - public class UserActivityTracker : IUserActivityTracker - { - private readonly PersonalisationGroupsConfig _config; - private readonly ICookieProvider _cookieProvider; - private readonly IDateTimeProvider _dateTimeProvider; - - public UserActivityTracker(IOptions config, ICookieProvider cookieProvider, IDateTimeProvider dateTimeProvider) - { - _config = config.Value; - _cookieProvider = cookieProvider; - _dateTimeProvider = dateTimeProvider; - } - - public void TrackPageView(int pageId) - { - var key = _config.CookieKeyForTrackingPagesViewed; - var value = _cookieProvider.GetCookieValue(key); - if (!string.IsNullOrEmpty(value)) - { - value = AppendPageIdIfNotPreviouslyViewed(value, pageId); - } - else - { - value = pageId.ToString(); - } - - var expires = _dateTimeProvider.GetCurrentDateTime().AddDays(_config.ViewedPagesTrackingCookieExpiryInDays); - _cookieProvider.SetCookie(key, value, expires); - } - - internal static string AppendPageIdIfNotPreviouslyViewed(string viewedPageIds, int pageId) - { - var ids = viewedPageIds.ParsePageIds(); - - if (!ids.Contains(pageId)) - { - ids.Add(pageId); - } - - return string.Join(",", ids); - } - - public void TrackSession() - { - var sessionCookieKey = _config.CookieKeyForTrackingIfSessionAlreadyTracked; - - // Check if session cookie present. - var sessionCookieExists = _cookieProvider.CookieExists(sessionCookieKey); - if (!sessionCookieExists) - { - // If not, create or update the number of visits cookie. - var numberOfVisitsCookeKey = _config.CookieKeyForTrackingNumberOfVisits; - var value = _cookieProvider.GetCookieValue(numberOfVisitsCookeKey); - if (!string.IsNullOrEmpty(value)) - { - value = int.TryParse(value, out int cookieValue) ? (cookieValue + 1).ToString() : "1"; - } - else - { - value = "1"; - } - - var expires = _dateTimeProvider.GetCurrentDateTime().AddDays(_config.NumberOfVisitsTrackingCookieExpiryInDays); - _cookieProvider.SetCookie(numberOfVisitsCookeKey, value, expires); - - // Set the session cookie so we don't keep updating on each request - _cookieProvider.SetCookie(_config.CookieKeyForTrackingIfSessionAlreadyTracked, "1"); - } - } - } -} \ No newline at end of file diff --git a/PersonalisationGroups.Core/ServicesConfiguration.cs b/PersonalisationGroups.Core/ServicesConfiguration.cs deleted file mode 100644 index b36935d..0000000 --- a/PersonalisationGroups.Core/ServicesConfiguration.cs +++ /dev/null @@ -1,103 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Middleware; -using Our.Umbraco.PersonalisationGroups.Core.Providers.AuthenticationStatus; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Cookie; -using Our.Umbraco.PersonalisationGroups.Core.Providers.DateTime; -using Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Host; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Ip; -using Our.Umbraco.PersonalisationGroups.Core.Providers.MemberGroup; -using Our.Umbraco.PersonalisationGroups.Core.Providers.MemberProfileField; -using Our.Umbraco.PersonalisationGroups.Core.Providers.MemberType; -using Our.Umbraco.PersonalisationGroups.Core.Providers.NumberOfVisits; -using Our.Umbraco.PersonalisationGroups.Core.Providers.PagesViewed; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Querystring; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Referrer; -using Our.Umbraco.PersonalisationGroups.Core.Providers.RequestHeaders; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Session; -using Our.Umbraco.PersonalisationGroups.Core.Services; -using System; -using Umbraco.Cms.Core.DependencyInjection; -using Umbraco.Cms.Web.Common.ApplicationBuilder; -using Umbraco.Extensions; - -namespace Our.Umbraco.PersonalisationGroups.Core -{ - public static class ServicesConfiguration - { - public static IUmbracoBuilder AddPersonalisationGroups(this IUmbracoBuilder builder, IConfiguration config) - { - var configSection = config.GetSection("Umbraco:PersonalisationGroups"); - AddConfiguration(builder.Services, configSection); - - AddServices(builder.Services); - - AddProviders(builder.Services, configSection); - - AddMiddleware(builder.Services, configSection); - - return builder; - } - - private static void AddConfiguration(IServiceCollection services, IConfigurationSection configSection) - { - services.Configure(configSection); - } - - private static void AddServices(IServiceCollection services) - { - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - } - - private static void AddProviders(IServiceCollection services, IConfigurationSection configSection) - { - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - switch (configSection.GetValue("CountryCodeProvider")) - { - case CountryCodeProvider.MaxMindDatabase: - services.AddUnique(); - break; - case CountryCodeProvider.CdnHeader: - services.AddUnique(); - break; - } - - services.AddUnique(); - services.AddUnique(); - services.AddSingleton(); - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - services.AddUnique(); - } - - private static void AddMiddleware(IServiceCollection services, IConfigurationSection configSection) - { - services.AddSingleton(); - - var disableUserActivityTracking = configSection.GetValue("DisableUserActivityTracking"); - if (!disableUserActivityTracking) - { - services.Configure(options => - options.AddFilter(new UmbracoPipelineFilter(nameof(TrackUserActivityMiddleware)) - { - PostPipeline = app => app.UseMiddleware() - })); - } - } - } -} diff --git a/PersonalisationGroups.Core/UmbracoPersonalisationGroups.Core.csproj b/PersonalisationGroups.Core/UmbracoPersonalisationGroups.Core.csproj deleted file mode 100644 index 79b126d..0000000 --- a/PersonalisationGroups.Core/UmbracoPersonalisationGroups.Core.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - - Our.Umbraco.PersonalisationGroups.Core - UmbracoPersonalisationGroups.Core - Our.Umbraco.PersonalisationGroups.Core - Core library for UmbracoPersonalisationGroups, an Umbraco 9+ package supporting personalisation of content to different groups of site visitors. - - - - - - - - - - - - - - <_Parameter1>Our.Umbraco.PersonalisationGroups.Tests - - - - - - - - diff --git a/PersonalisationGroups.Tests/Criteria/AuthenticationStatus/AuthenticationStatusPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/AuthenticationStatus/AuthenticationStatusPersonalisationGroupCriteriaTests.cs index 9d2952e..0e74a66 100644 --- a/PersonalisationGroups.Tests/Criteria/AuthenticationStatus/AuthenticationStatusPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/AuthenticationStatus/AuthenticationStatusPersonalisationGroupCriteriaTests.cs @@ -1,106 +1,105 @@ using System; using NUnit.Framework; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.AuthenticationStatus; -using Our.Umbraco.PersonalisationGroups.Core.Providers.AuthenticationStatus; +using Our.Umbraco.PersonalisationGroups.Criteria.AuthenticationStatus; +using Our.Umbraco.PersonalisationGroups.Providers.AuthenticationStatus; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.AuthenticationStatus +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.AuthenticationStatus; + +[TestFixture] +public class AuthenticationStatusPersonalisationGroupCriteriaTests { - [TestFixture] - public class AuthenticationStatusPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"isAuthenticated\": {0} }}"; + + [Test] + public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(); + var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(); + var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionIsAuthenticated_WithAuthenticatedMember_ReturnsTrue() + { + // Arrange + var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(true); + var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); + var definition = string.Format(DefinitionFormat, "true"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionIsAuthenticated_WithUnauthenticatedMember_ReturnsTrue() + { + // Arrange + var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(); + var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); + var definition = string.Format(DefinitionFormat, "true"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionIsUnauthenticated_WithAuthenticatedMember_ReturnsFalse() + { + // Arrange + var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(true); + var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); + var definition = string.Format(DefinitionFormat, "false"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionIsUnauthenticated_WithUnauthenticatedMember_ReturnsTrue() + { + // Arrange + var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(); + var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); + var definition = string.Format(DefinitionFormat, "false"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + private static Mock MockAuthenticationStatusProvider(bool authenticationStatus = false) { - private const string DefinitionFormat = "{{ \"isAuthenticated\": {0} }}"; - - [Test] - public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(); - var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(); - var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionIsAuthenticated_WithAuthenticatedMember_ReturnsTrue() - { - // Arrange - var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(true); - var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); - var definition = string.Format(DefinitionFormat, "true"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionIsAuthenticated_WithUnauthenticatedMember_ReturnsTrue() - { - // Arrange - var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(); - var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); - var definition = string.Format(DefinitionFormat, "true"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionIsUnauthenticated_WithAuthenticatedMember_ReturnsFalse() - { - // Arrange - var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(true); - var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); - var definition = string.Format(DefinitionFormat, "false"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void AuthenticationStatusPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionIsUnauthenticated_WithUnauthenticatedMember_ReturnsTrue() - { - // Arrange - var mockAuthenticationStatusProvider = MockAuthenticationStatusProvider(); - var criteria = new AuthenticationStatusPersonalisationGroupCriteria(mockAuthenticationStatusProvider.Object); - var definition = string.Format(DefinitionFormat, "false"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - private static Mock MockAuthenticationStatusProvider(bool authenticationStatus = false) - { - var mock = new Mock(); - - mock.Setup(x => x.IsAuthenticated()).Returns(authenticationStatus); - - return mock; - } + var mock = new Mock(); + + mock.Setup(x => x.IsAuthenticated()).Returns(authenticationStatus); + + return mock; } } \ No newline at end of file diff --git a/PersonalisationGroups.Tests/Criteria/Continent/ContinentPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/Continent/ContinentPersonalisationGroupCriteriaTests.cs index 7b1c55a..9844f65 100644 --- a/PersonalisationGroups.Tests/Criteria/Continent/ContinentPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/Continent/ContinentPersonalisationGroupCriteriaTests.cs @@ -1,176 +1,175 @@ using System; using NUnit.Framework; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.Continent; -using Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Ip; +using Our.Umbraco.PersonalisationGroups.Criteria.Continent; +using Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; +using Our.Umbraco.PersonalisationGroups.Providers.Ip; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Continent +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Continent; + +[TestFixture] +public class ContinentPersonalisationGroupCriteriaTests { - [TestFixture] - public class ContinentPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"match\": \"{0}\", \"codes\": [ \"{1}\", \"{2}\" ] }}"; + + [Test] + public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockContinentGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockContinentGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithEmptyContinentLists_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockContinentGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); + var definition = "{ \"match\": \"IsLocatedIn\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentContinentList_ReturnsFalse() { - private const string DefinitionFormat = "{{ \"match\": \"{0}\", \"codes\": [ \"{1}\", \"{2}\" ] }}"; - - [Test] - public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockContinentGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockContinentGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithEmptyContinentLists_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockContinentGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); - var definition = "{ \"match\": \"IsLocatedIn\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentContinentList_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockContinentGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); - var definition = string.Format(DefinitionFormat, "IsLocatedIn", "AF", "AS"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingContinentList_ReturnsTrue() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockContinentGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); - var definition = string.Format(DefinitionFormat, "IsLocatedIn", "EU", "AF"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentContinentListAndNotInCheck_ReturnsTrue() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockContinentGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); - var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "AF", "AS"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingContinentListAndNotInCheck_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockContinentGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); - var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "EU", "AF"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCannotLocate_ReturnsTrue() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockContinentGeoLocationProvider = MockGeoLocationProvider(canGeolocate: false); - var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); - var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCanLocate_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockContinentGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); - var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - private static Mock MockIpProvider() - { - var mock = new Mock(); - - mock.Setup(x => x.GetIp()).Returns("1.2.3.4"); - - return mock; - } - - private static Mock MockGeoLocationProvider(bool canGeolocate = true) - { - var mock = new Mock(); - - mock.Setup(x => x.GetContinentFromIp(It.IsAny())) - .Returns(canGeolocate - ? new Core.Providers.GeoLocation.Continent - { - Code = "EU", Name = "Europe" - } - : null); - - return mock; - } + // Arrange + var mockIpProvider = MockIpProvider(); + var mockContinentGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); + var definition = string.Format(DefinitionFormat, "IsLocatedIn", "AF", "AS"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingContinentList_ReturnsTrue() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockContinentGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); + var definition = string.Format(DefinitionFormat, "IsLocatedIn", "EU", "AF"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentContinentListAndNotInCheck_ReturnsTrue() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockContinentGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); + var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "AF", "AS"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingContinentListAndNotInCheck_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockContinentGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); + var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "EU", "AF"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCannotLocate_ReturnsTrue() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockContinentGeoLocationProvider = MockGeoLocationProvider(canGeolocate: false); + var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); + var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void ContinentPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCanLocate_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockContinentGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new ContinentPersonalisationGroupCriteria(mockIpProvider.Object, mockContinentGeoLocationProvider.Object); + var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + private static Mock MockIpProvider() + { + var mock = new Mock(); + + mock.Setup(x => x.GetIp()).Returns("1.2.3.4"); + + return mock; + } + + private static Mock MockGeoLocationProvider(bool canGeolocate = true) + { + var mock = new Mock(); + + mock.Setup(x => x.GetContinentFromIp(It.IsAny())) + .Returns(canGeolocate + ? new PersonalisationGroups.Providers.GeoLocation.Continent + { + Code = "EU", Name = "Europe" + } + : null); + + return mock; } } diff --git a/PersonalisationGroups.Tests/Criteria/Cookie/CookiePersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/Cookie/CookiePersonalisationGroupCriteriaTests.cs index eb7c872..f1bbb96 100644 --- a/PersonalisationGroups.Tests/Criteria/Cookie/CookiePersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/Cookie/CookiePersonalisationGroupCriteriaTests.cs @@ -1,432 +1,431 @@ using System; using NUnit.Framework; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.Cookie; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Cookie; +using Our.Umbraco.PersonalisationGroups.Criteria.Cookie; +using Our.Umbraco.PersonalisationGroups.Providers.Cookie; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Cookie +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Cookie; + + +[TestFixture] +public class CookiePersonalisationGroupCriteriaTests { + private const string DefinitionFormat = "{{ \"key\": \"{0}\", \"match\": \"{1}\", \"value\": \"{2}\" }}"; + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieExists_WithExistingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "Exists", string.Empty); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieExists_WithMissingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "missing-key", "Exists", string.Empty); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieAbsent_WithAbsentCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "missing-key", "DoesNotExist", string.Empty); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieAbsent_WithExistingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "DoesNotExist", string.Empty); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieMatchingValue_WithMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,ccc"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieMatchingValue_WithNonMatchingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,xxx"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieContainingValue_WithMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "bbb"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieContainingValue_WithNonMatchingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "xxx"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-APR-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithNonMatchingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-JUN-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "3"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithNonMatchingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "7"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "aaa"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithNonMatchingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "ccc"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-JUN-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithNonMatchingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-APR-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "7"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithNonMatchingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "3"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "ccc"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithNonMatchingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "aaa"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[a-z]"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithNonMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[A-Z]"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithMissingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "missing-key", "DoesNotMatchRegex", "[a-z]"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithMatchingCookie_ReturnsFalse() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[a-z]"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithNonMatchingCookie_ReturnsTrue() + { + // Arrange + var mockCookieProvider = MockCookieProvider(); + var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[A-Z]"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } - [TestFixture] - public class CookiePersonalisationGroupCriteriaTests + private static Mock MockCookieProvider() { - private const string DefinitionFormat = "{{ \"key\": \"{0}\", \"match\": \"{1}\", \"value\": \"{2}\" }}"; - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieExists_WithExistingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "Exists", string.Empty); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieExists_WithMissingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "missing-key", "Exists", string.Empty); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieAbsent_WithAbsentCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "missing-key", "DoesNotExist", string.Empty); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieAbsent_WithExistingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "DoesNotExist", string.Empty); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieMatchingValue_WithMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,ccc"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieMatchingValue_WithNonMatchingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,xxx"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieContainingValue_WithMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "bbb"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForCookieContainingValue_WithNonMatchingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "xxx"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-APR-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithNonMatchingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-JUN-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "3"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithNonMatchingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "7"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "aaa"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithNonMatchingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "ccc"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-JUN-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithNonMatchingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-APR-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "7"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithNonMatchingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "3"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "ccc"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithNonMatchingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "aaa"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[a-z]"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithNonMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[A-Z]"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithMissingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "missing-key", "DoesNotMatchRegex", "[a-z]"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithMatchingCookie_ReturnsFalse() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[a-z]"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void CookiePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithNonMatchingCookie_ReturnsTrue() - { - // Arrange - var mockCookieProvider = MockCookieProvider(); - var criteria = new CookiePersonalisationGroupCriteria(mockCookieProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[A-Z]"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - private static Mock MockCookieProvider() - { - var mock = new Mock(); - - mock.Setup(x => x.CookieExists(It.Is(y => y == "key"))).Returns(true); - mock.Setup(x => x.CookieExists(It.Is(y => y == "dateCompareTest"))).Returns(true); - mock.Setup(x => x.CookieExists(It.Is(y => y == "numericCompareTest"))).Returns(true); - mock.Setup(x => x.CookieExists(It.Is(y => y == "stringCompareTest"))).Returns(true); - mock.Setup(x => x.CookieExists(It.Is(y => y == "missing-key"))).Returns(false); - mock.Setup(x => x.CookieExists(It.Is(y => y == "regexTest"))).Returns(true); - mock.Setup(x => x.GetCookieValue(It.Is(y => y == "key"))).Returns("aaa,bbb,ccc"); - mock.Setup(x => x.GetCookieValue(It.Is(y => y == "dateCompareTest"))).Returns("1-MAY-2015 10:30:00"); - mock.Setup(x => x.GetCookieValue(It.Is(y => y == "numericCompareTest"))).Returns("5"); - mock.Setup(x => x.GetCookieValue(It.Is(y => y == "stringCompareTest"))).Returns("bbb"); - mock.Setup(x => x.GetCookieValue(It.Is(y => y == "regexTest"))).Returns("b"); - - return mock; - } + var mock = new Mock(); + + mock.Setup(x => x.CookieExists(It.Is(y => y == "key"))).Returns(true); + mock.Setup(x => x.CookieExists(It.Is(y => y == "dateCompareTest"))).Returns(true); + mock.Setup(x => x.CookieExists(It.Is(y => y == "numericCompareTest"))).Returns(true); + mock.Setup(x => x.CookieExists(It.Is(y => y == "stringCompareTest"))).Returns(true); + mock.Setup(x => x.CookieExists(It.Is(y => y == "missing-key"))).Returns(false); + mock.Setup(x => x.CookieExists(It.Is(y => y == "regexTest"))).Returns(true); + mock.Setup(x => x.GetCookieValue(It.Is(y => y == "key"))).Returns("aaa,bbb,ccc"); + mock.Setup(x => x.GetCookieValue(It.Is(y => y == "dateCompareTest"))).Returns("1-MAY-2015 10:30:00"); + mock.Setup(x => x.GetCookieValue(It.Is(y => y == "numericCompareTest"))).Returns("5"); + mock.Setup(x => x.GetCookieValue(It.Is(y => y == "stringCompareTest"))).Returns("bbb"); + mock.Setup(x => x.GetCookieValue(It.Is(y => y == "regexTest"))).Returns("b"); + + return mock; } } diff --git a/PersonalisationGroups.Tests/Criteria/Country/CountryPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/Country/CountryPersonalisationGroupCriteriaTests.cs index 5a73e57..b72223f 100644 --- a/PersonalisationGroups.Tests/Criteria/Country/CountryPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/Country/CountryPersonalisationGroupCriteriaTests.cs @@ -2,366 +2,365 @@ using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.Country; -using Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Ip; -using Our.Umbraco.PersonalisationGroups.Core.Providers.RequestHeaders; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Criteria.Country; +using Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; +using Our.Umbraco.PersonalisationGroups.Providers.Ip; +using Our.Umbraco.PersonalisationGroups.Providers.RequestHeaders; using System; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Country +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Country; + +[TestFixture] +public class CountryPersonalisationGroupCriteriaTests { - [TestFixture] - public class CountryPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"match\": \"{0}\", \"codes\": [ \"{1}\", \"{2}\" ] }}"; + + [Test] + public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithEmptyDefinition_ThrowsException() { - private const string DefinitionFormat = "{{ \"match\": \"{0}\", \"codes\": [ \"{1}\", \"{2}\" ] }}"; + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithEmptyCountryLists_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = "{ \"match\": \"IsLocatedIn\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithEmptyCountryLists_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = "{ \"match\": \"IsLocatedIn\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithDifferentCountryList_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = string.Format(DefinitionFormat, "IsLocatedIn", "ES", "IT"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithDifferentCountryList_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = string.Format(DefinitionFormat, "IsLocatedIn", "ES", "IT"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithMatchingCountryList_ReturnsTrue() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = string.Format(DefinitionFormat, "IsLocatedIn", "GB", "IT"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithMatchingCountryList_ReturnsTrue() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = string.Format(DefinitionFormat, "IsLocatedIn", "GB", "IT"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithDifferentCountryListAndNotInCheck_ReturnsTrue() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "ES", "IT"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithDifferentCountryListAndNotInCheck_ReturnsTrue() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "ES", "IT"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithMatchingCountryListAndNotInCheck_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "GB", "IT"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithMatchingCountryListAndNotInCheck_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "GB", "IT"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCannotLocate_ReturnsTrue() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(canGeolocate: false); + var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCannotLocate_ReturnsTrue() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(canGeolocate: false); - var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCanLocate_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingMockedMaxMindDatabaseCountryCodeProvider_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCanLocate_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var countryCodeProvider = new MaxMindCountryCodeFromIpProvider(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var config = Options.Create(new PersonalisationGroupsConfig()); + var mockRequestHeadersProvider = MockRequestHeadersProvider(); + var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var config = Options.Create(new PersonalisationGroupsConfig()); - var mockRequestHeadersProvider = MockRequestHeadersProvider(); - var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var config = Options.Create(new PersonalisationGroupsConfig()); + var mockRequestHeadersProvider = MockRequestHeadersProvider(); + var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var config = Options.Create(new PersonalisationGroupsConfig()); - var mockRequestHeadersProvider = MockRequestHeadersProvider(); - var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithEmptyCountryLists_ReturnsFalse() + { + // Arrange + var config = Options.Create(new PersonalisationGroupsConfig()); + var mockRequestHeadersProvider = MockRequestHeadersProvider(); + var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = "{ \"match\": \"IsLocatedIn\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithEmptyCountryLists_ReturnsFalse() - { - // Arrange - var config = Options.Create(new PersonalisationGroupsConfig()); - var mockRequestHeadersProvider = MockRequestHeadersProvider(); - var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = "{ \"match\": \"IsLocatedIn\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithDifferentCountryList_ReturnsFalse() + { + // Arrange + var config = Options.Create(new PersonalisationGroupsConfig()); + var mockRequestHeadersProvider = MockRequestHeadersProvider(); + var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = string.Format(DefinitionFormat, "IsLocatedIn", "ES", "IT"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithDifferentCountryList_ReturnsFalse() - { - // Arrange - var config = Options.Create(new PersonalisationGroupsConfig()); - var mockRequestHeadersProvider = MockRequestHeadersProvider(); - var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = string.Format(DefinitionFormat, "IsLocatedIn", "ES", "IT"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithMatchingCountryList_ReturnsTrue() + { + // Arrange + var config = Options.Create(new PersonalisationGroupsConfig()); + var mockRequestHeadersProvider = MockRequestHeadersProvider(); + var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = string.Format(DefinitionFormat, "IsLocatedIn", "GB", "IT"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithMatchingCountryList_ReturnsTrue() - { - // Arrange - var config = Options.Create(new PersonalisationGroupsConfig()); - var mockRequestHeadersProvider = MockRequestHeadersProvider(); - var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = string.Format(DefinitionFormat, "IsLocatedIn", "GB", "IT"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithDifferentCountryListAndNotInCheck_ReturnsTrue() + { + // Arrange + var config = Options.Create(new PersonalisationGroupsConfig()); + var mockRequestHeadersProvider = MockRequestHeadersProvider(); + var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "ES", "IT"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithDifferentCountryListAndNotInCheck_ReturnsTrue() - { - // Arrange - var config = Options.Create(new PersonalisationGroupsConfig()); - var mockRequestHeadersProvider = MockRequestHeadersProvider(); - var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "ES", "IT"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithMatchingCountryListAndNotInCheck_ReturnsFalse() + { + // Arrange + var config = Options.Create(new PersonalisationGroupsConfig()); + var mockRequestHeadersProvider = MockRequestHeadersProvider(); + var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "GB", "IT"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionWithMatchingCountryListAndNotInCheck_ReturnsFalse() - { - // Arrange - var config = Options.Create(new PersonalisationGroupsConfig()); - var mockRequestHeadersProvider = MockRequestHeadersProvider(); - var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "GB", "IT"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCannotLocateAsNoHeader_ReturnsTrue() + { + // Arrange + var config = Options.Create(new PersonalisationGroupsConfig()); + var mockRequestHeadersProvider = MockRequestHeadersProvider(withHeader: false); + var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCannotLocateAsNoHeader_ReturnsTrue() - { - // Arrange - var config = Options.Create(new PersonalisationGroupsConfig()); - var mockRequestHeadersProvider = MockRequestHeadersProvider(withHeader: false); - var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCannotLocateAsEmptyHeader_ReturnsTrue() + { + // Arrange + var config = Options.Create(new PersonalisationGroupsConfig()); + var mockRequestHeadersProvider = MockRequestHeadersProvider(withValue: false); + var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCannotLocateAsEmptyHeader_ReturnsTrue() - { - // Arrange - var config = Options.Create(new PersonalisationGroupsConfig()); - var mockRequestHeadersProvider = MockRequestHeadersProvider(withValue: false); - var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } + [Test] + public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCanLocate_ReturnsFalse() + { + // Arrange + var config = Options.Create(new PersonalisationGroupsConfig()); + var mockRequestHeadersProvider = MockRequestHeadersProvider(); + var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); + var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); + var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } - [Test] - public void CountryPersonalisationGroupCriteria_UsingCloudFlareCdnHeaderCountryCodeProvider_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCanLocate_ReturnsFalse() - { - // Arrange - var config = Options.Create(new PersonalisationGroupsConfig()); - var mockRequestHeadersProvider = MockRequestHeadersProvider(); - var countryCodeProvider = new CdnHeaderCountryCodeProvider(config, mockRequestHeadersProvider.Object); - var criteria = new CountryPersonalisationGroupCriteria(countryCodeProvider); - var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } + private static Mock MockIpProvider() + { + var mock = new Mock(); - private static Mock MockIpProvider() - { - var mock = new Mock(); + mock.Setup(x => x.GetIp()).Returns("1.2.3.4"); - mock.Setup(x => x.GetIp()).Returns("1.2.3.4"); + return mock; + } - return mock; - } + private static Mock MockGeoLocationProvider(bool canGeolocate = true) + { + var mock = new Mock(); - private static Mock MockGeoLocationProvider(bool canGeolocate = true) - { - var mock = new Mock(); + mock.Setup(x => x.GetCountryFromIp(It.IsAny())) + .Returns(canGeolocate + ? new PersonalisationGroups.Providers.GeoLocation.Country + { + Code = "GB", Name = "United Kingdom" + } + : null); - mock.Setup(x => x.GetCountryFromIp(It.IsAny())) - .Returns(canGeolocate - ? new Core.Providers.GeoLocation.Country - { - Code = "GB", Name = "United Kingdom" - } - : null); + return mock; + } - return mock; - } + private static Mock MockRequestHeadersProvider(bool withHeader = true, bool withValue = true) + { + var mock = new Mock(); - private static Mock MockRequestHeadersProvider(bool withHeader = true, bool withValue = true) + var resultHeaders = new HeaderDictionary(); + if (withHeader) { - var mock = new Mock(); - - var resultHeaders = new HeaderDictionary(); - if (withHeader) - { - resultHeaders.Add(Core.AppConstants.DefaultCdnCountryCodeHttpHeaderName, withValue ? "GB" : string.Empty); - } + resultHeaders.Add(AppConstants.DefaultCdnCountryCodeHttpHeaderName, withValue ? "GB" : string.Empty); + } - mock.Setup(x => x.GetHeaders()).Returns(resultHeaders); + mock.Setup(x => x.GetHeaders()).Returns(resultHeaders); - return mock; - } + return mock; } } diff --git a/PersonalisationGroups.Tests/Criteria/DateTimeCriteriaTestsBase.cs b/PersonalisationGroups.Tests/Criteria/DateTimeCriteriaTestsBase.cs index 6b32caa..d998671 100644 --- a/PersonalisationGroups.Tests/Criteria/DateTimeCriteriaTestsBase.cs +++ b/PersonalisationGroups.Tests/Criteria/DateTimeCriteriaTestsBase.cs @@ -1,19 +1,18 @@ using System; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Providers.DateTime; +using Our.Umbraco.PersonalisationGroups.Providers.DateTime; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria; + +public abstract class DateTimeCriteriaTestsBase { - public abstract class DateTimeCriteriaTestsBase + protected static Mock MockDateTimeProvider(DateTime? dateTime = null) { - protected static Mock MockDateTimeProvider(DateTime? dateTime = null) - { - dateTime = dateTime ?? new DateTime(2016, 6, 3, 10, 0, 0); // a Friday in June - var mock = new Mock(); + dateTime = dateTime ?? new DateTime(2016, 6, 3, 10, 0, 0); // a Friday in June + var mock = new Mock(); - mock.Setup(x => x.GetCurrentDateTime()).Returns(dateTime.Value); + mock.Setup(x => x.GetCurrentDateTime()).Returns(dateTime.Value); - return mock; - } + return mock; } } diff --git a/PersonalisationGroups.Tests/Criteria/DayOfWeek/DayOfWeekPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/DayOfWeek/DayOfWeekPersonalisationGroupCriteriaTests.cs index 7c6546e..d140422 100644 --- a/PersonalisationGroups.Tests/Criteria/DayOfWeek/DayOfWeekPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/DayOfWeek/DayOfWeekPersonalisationGroupCriteriaTests.cs @@ -1,80 +1,79 @@ using System; using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.DayOfWeek; +using Our.Umbraco.PersonalisationGroups.Criteria.DayOfWeek; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.DayOfWeek +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.DayOfWeek; + +[TestFixture] +public class DayOfWeekPersonalisationGroupCriteriaTests : DateTimeCriteriaTestsBase { - [TestFixture] - public class DayOfWeekPersonalisationGroupCriteriaTests : DateTimeCriteriaTestsBase - { - private const string DefinitionFormat = "[ {0}, {1} ]"; + private const string DefinitionFormat = "[ {0}, {1} ]"; - [Test] - public void DayOfWeekPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new DayOfWeekPersonalisationGroupCriteria(mockDateTimeProvider.Object); + [Test] + public void DayOfWeekPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new DayOfWeekPersonalisationGroupCriteria(mockDateTimeProvider.Object); - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } - [Test] - public void DayOfWeekPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new DayOfWeekPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = "invalid"; + [Test] + public void DayOfWeekPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new DayOfWeekPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = "invalid"; - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } - [Test] - public void DayOfWeekPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithEmptyDays_ReturnsFalse() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new DayOfWeekPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = "[]"; + [Test] + public void DayOfWeekPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithEmptyDays_ReturnsFalse() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new DayOfWeekPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = "[]"; - // Act - var result = criteria.MatchesVisitor(definition); + // Act + var result = criteria.MatchesVisitor(definition); - // Assert - Assert.IsFalse(result); - } + // Assert + Assert.IsFalse(result); + } - [Test] - public void DayOfWeekPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentDays_ReturnsFalse() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new DayOfWeekPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = string.Format(DefinitionFormat, 2, 3); // Monday, Tuesday + [Test] + public void DayOfWeekPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentDays_ReturnsFalse() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new DayOfWeekPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = string.Format(DefinitionFormat, 2, 3); // Monday, Tuesday - // Act - var result = criteria.MatchesVisitor(definition); + // Act + var result = criteria.MatchesVisitor(definition); - // Assert - Assert.IsFalse(result); - } + // Assert + Assert.IsFalse(result); + } - [Test] - public void DayOfWeekPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingDays_ReturnsTrue() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new DayOfWeekPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = string.Format(DefinitionFormat, 6, 7); // Friday, Saturday + [Test] + public void DayOfWeekPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingDays_ReturnsTrue() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new DayOfWeekPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = string.Format(DefinitionFormat, 6, 7); // Friday, Saturday - // Act - var result = criteria.MatchesVisitor(definition); + // Act + var result = criteria.MatchesVisitor(definition); - // Assert - Assert.IsTrue(result); - } + // Assert + Assert.IsTrue(result); } } diff --git a/PersonalisationGroups.Tests/Criteria/Host/HostPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/Host/HostPersonalisationGroupCriteriaTests.cs index 66726c6..ba57076 100644 --- a/PersonalisationGroups.Tests/Criteria/Host/HostPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/Host/HostPersonalisationGroupCriteriaTests.cs @@ -1,166 +1,165 @@ -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Host +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Host; + +using System; +using NUnit.Framework; +using Moq; +using Our.Umbraco.PersonalisationGroups.Criteria.Host; +using Our.Umbraco.PersonalisationGroups.Providers.Host; + +[TestFixture] +public class HostPersonalisationGroupCriteriaTests { - using System; - using NUnit.Framework; - using Moq; - using Our.Umbraco.PersonalisationGroups.Core.Criteria.Host; - using Our.Umbraco.PersonalisationGroups.Core.Providers.Host; - - [TestFixture] - public class HostPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"value\": \"{0}\", \"match\": \"{1}\" }}"; + + [Test] + public void HostPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockHostProvider = MockHostProvider(); + var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void HostPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockHostProvider = MockHostProvider(); + var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void HostPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForHostMatches_WithMatchingValue_ReturnsTrue() + { + // Arrange + var mockHostProvider = MockHostProvider(); + var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); + var definition = string.Format(DefinitionFormat, "http://www.example.com/", "MatchesValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void HostPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForHostMatches_WithNonMatchingValue_ReturnsFalse() + { + // Arrange + var mockHostProvider = MockHostProvider(); + var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); + var definition = string.Format(DefinitionFormat, "http://www.another-example.com/", "MatchesValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void HostPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForHostDoesNotMatch_WithNonMatchingValue_ReturnsTrue() + { + // Arrange + var mockHostProvider = MockHostProvider(); + var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); + var definition = string.Format(DefinitionFormat, "http://www.another-example.com/", "DoesNotMatchValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void HostPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForHostDoesNotMatch_WithMatchingValue_ReturnsFalse() { - private const string DefinitionFormat = "{{ \"value\": \"{0}\", \"match\": \"{1}\" }}"; - - [Test] - public void HostPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockHostProvider = MockHostProvider(); - var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void HostPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockHostProvider = MockHostProvider(); - var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void HostPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForHostMatches_WithMatchingValue_ReturnsTrue() - { - // Arrange - var mockHostProvider = MockHostProvider(); - var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); - var definition = string.Format(DefinitionFormat, "http://www.example.com/", "MatchesValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void HostPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForHostMatches_WithNonMatchingValue_ReturnsFalse() - { - // Arrange - var mockHostProvider = MockHostProvider(); - var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); - var definition = string.Format(DefinitionFormat, "http://www.another-example.com/", "MatchesValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void HostPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForHostDoesNotMatch_WithNonMatchingValue_ReturnsTrue() - { - // Arrange - var mockHostProvider = MockHostProvider(); - var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); - var definition = string.Format(DefinitionFormat, "http://www.another-example.com/", "DoesNotMatchValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void HostPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForHostDoesNotMatch_WithMatchingValue_ReturnsFalse() - { - // Arrange - var mockHostProvider = MockHostProvider(); - var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); - var definition = string.Format(DefinitionFormat, "http://www.example.com/", "DoesNotMatchValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void HostPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForHostContains_WithMatchingValue_ReturnsTrue() - { - // Arrange - var mockHostProvider = MockHostProvider(); - var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); - var definition = string.Format(DefinitionFormat, "example", "ContainsValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void HostPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForHostContains_WithNonMatchingValue_ReturnsFalse() - { - // Arrange - var mockHostProvider = MockHostProvider(); - var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); - var definition = string.Format(DefinitionFormat, "another-example", "ContainsValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void HostPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForHostDoesNotMatch_WithNonMatchingValue_ReturnsTrue() - { - // Arrange - var mockHostProvider = MockHostProvider(); - var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); - var definition = string.Format(DefinitionFormat, "another-example", "DoesNotContainValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void HostPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForHostDoesNotMatch_WithMatchingValue_ReturnsFalse() - { - // Arrange - var mockHostProvider = MockHostProvider(); - var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); - var definition = string.Format(DefinitionFormat, "example", "DoesNotContainValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - private static Mock MockHostProvider() - { - var mock = new Mock(); - - mock.Setup(x => x.GetHost()).Returns("http://www.example.com/"); - - return mock; - } + // Arrange + var mockHostProvider = MockHostProvider(); + var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); + var definition = string.Format(DefinitionFormat, "http://www.example.com/", "DoesNotMatchValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void HostPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForHostContains_WithMatchingValue_ReturnsTrue() + { + // Arrange + var mockHostProvider = MockHostProvider(); + var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); + var definition = string.Format(DefinitionFormat, "example", "ContainsValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void HostPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForHostContains_WithNonMatchingValue_ReturnsFalse() + { + // Arrange + var mockHostProvider = MockHostProvider(); + var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); + var definition = string.Format(DefinitionFormat, "another-example", "ContainsValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void HostPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForHostDoesNotMatch_WithNonMatchingValue_ReturnsTrue() + { + // Arrange + var mockHostProvider = MockHostProvider(); + var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); + var definition = string.Format(DefinitionFormat, "another-example", "DoesNotContainValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void HostPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForHostDoesNotMatch_WithMatchingValue_ReturnsFalse() + { + // Arrange + var mockHostProvider = MockHostProvider(); + var criteria = new HostPersonalisationGroupCriteria(mockHostProvider.Object); + var definition = string.Format(DefinitionFormat, "example", "DoesNotContainValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + private static Mock MockHostProvider() + { + var mock = new Mock(); + + mock.Setup(x => x.GetHost()).Returns("http://www.example.com/"); + + return mock; } } diff --git a/PersonalisationGroups.Tests/Criteria/MemberGroup/MemberTypePersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/MemberGroup/MemberTypePersonalisationGroupCriteriaTests.cs index d78bbfd..35fd37d 100644 --- a/PersonalisationGroups.Tests/Criteria/MemberGroup/MemberTypePersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/MemberGroup/MemberTypePersonalisationGroupCriteriaTests.cs @@ -1,106 +1,105 @@ using System; using NUnit.Framework; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.MemberGroup; -using Our.Umbraco.PersonalisationGroups.Core.Providers.MemberGroup; +using Our.Umbraco.PersonalisationGroups.Criteria.MemberGroup; +using Our.Umbraco.PersonalisationGroups.Providers.MemberGroup; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.MemberGroup +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.MemberGroup; + +[TestFixture] +public class MemberGroupPersonalisationGroupCriteriaTests { - [TestFixture] - public class MemberGroupPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"groupName\": \"{0}\", \"match\": \"{1}\" }}"; + + [Test] + public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockMemberGroupProvider = MockMemberGroupProvider(); + var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockMemberGroupProvider = MockMemberGroupProvider(); + var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingGroup_WithMatchingGroup_ReturnsTrue() + { + // Arrange + var mockMemberGroupProvider = MockMemberGroupProvider(); + var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); + var definition = string.Format(DefinitionFormat, "Group A", "IsInGroup"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingGroup_WithNonMatchingGroup_ReturnsFalse() + { + // Arrange + var mockMemberGroupProvider = MockMemberGroupProvider("Group B"); + var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); + var definition = string.Format(DefinitionFormat, "Group A", "IsInGroup"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionNotMatchingGroup_WithMatchingGroup_ReturnsFalse() + { + // Arrange + var mockMemberGroupProvider = MockMemberGroupProvider(); + var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); + var definition = string.Format(DefinitionFormat, "Group A", "IsNotInGroup"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionNotMatchingGroup_WithNonMatchingGroup_ReturnsTrue() + { + // Arrange + var mockMemberGroupProvider = MockMemberGroupProvider("Group B"); + var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); + var definition = string.Format(DefinitionFormat, "Group A", "IsNotInGroup"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + private static Mock MockMemberGroupProvider(string groupName = "Group A") { - private const string DefinitionFormat = "{{ \"groupName\": \"{0}\", \"match\": \"{1}\" }}"; - - [Test] - public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockMemberGroupProvider = MockMemberGroupProvider(); - var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockMemberGroupProvider = MockMemberGroupProvider(); - var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingGroup_WithMatchingGroup_ReturnsTrue() - { - // Arrange - var mockMemberGroupProvider = MockMemberGroupProvider(); - var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); - var definition = string.Format(DefinitionFormat, "Group A", "IsInGroup"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingGroup_WithNonMatchingGroup_ReturnsFalse() - { - // Arrange - var mockMemberGroupProvider = MockMemberGroupProvider("Group B"); - var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); - var definition = string.Format(DefinitionFormat, "Group A", "IsInGroup"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionNotMatchingGroup_WithMatchingGroup_ReturnsFalse() - { - // Arrange - var mockMemberGroupProvider = MockMemberGroupProvider(); - var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); - var definition = string.Format(DefinitionFormat, "Group A", "IsNotInGroup"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberGroupPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionNotMatchingGroup_WithNonMatchingGroup_ReturnsTrue() - { - // Arrange - var mockMemberGroupProvider = MockMemberGroupProvider("Group B"); - var criteria = new MemberGroupPersonalisationGroupCriteria(mockMemberGroupProvider.Object); - var definition = string.Format(DefinitionFormat, "Group A", "IsNotInGroup"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - private static Mock MockMemberGroupProvider(string groupName = "Group A") - { - var mock = new Mock(); - - mock.Setup(x => x.GetMemberGroups()).Returns(new string[] { groupName }); - - return mock; - } + var mock = new Mock(); + + mock.Setup(x => x.GetMemberGroups()).Returns(new string[] { groupName }); + + return mock; } } \ No newline at end of file diff --git a/PersonalisationGroups.Tests/Criteria/MemberProfileField/MemberProfileFieldPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/MemberProfileField/MemberProfileFieldPersonalisationGroupCriteriaTests.cs index e5536f6..5acf227 100644 --- a/PersonalisationGroups.Tests/Criteria/MemberProfileField/MemberProfileFieldPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/MemberProfileField/MemberProfileFieldPersonalisationGroupCriteriaTests.cs @@ -1,289 +1,288 @@ using System; using NUnit.Framework; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.MemberProfileField; -using Our.Umbraco.PersonalisationGroups.Core.Providers.MemberProfileField; +using Our.Umbraco.PersonalisationGroups.Criteria.MemberProfileField; +using Our.Umbraco.PersonalisationGroups.Providers.MemberProfileField; -namespace Zone.UmbracoPersonalisationGroups.V8.Tests.Criteria.MemberProfileField +namespace Zone.UmbracoPersonalisationGroups.V8.Tests.Criteria.MemberProfileField; + +[TestFixture] +public class MemberProfileFieldPersonalisationGroupCriteriaTests { - [TestFixture] - public class MemberProfileFieldPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"alias\": \"{0}\", \"match\": \"{1}\", \"value\": \"{2}\" }}"; + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingProfileField_WithMatchingField_ReturnsTrue() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "abc", "MatchesValue", "xyz"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingProfileField_WithNonMatchingField_ReturnsFalse() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "abc", "MatchesValue", "zyx"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionNotMatchingProfileField_WithMatchingField_ReturnsFalse() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "abc", "DoesNotMatchValue", "xyz"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingProfileField_WithNonMatchingField_ReturnsTrue() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "abc", "DoesNotMatchValue", "zyx"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithMatchingMemberProfileField_ReturnsTrue() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-APR-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithNonMatchingMemberProfileField_ReturnsFalse() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-JUN-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithMatchingMemberProfileField_ReturnsTrue() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "3"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithNonMatchingMemberProfileField_ReturnsFalse() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "7"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithMatchingMemberProfileField_ReturnsTrue() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "aaa"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithNonMatchingMemberProfileField_ReturnsFalse() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "ccc"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithMatchingMemberProfileField_ReturnsTrue() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-JUN-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithNonMatchingMemberProfileField_ReturnsFalse() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-APR-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithMatchingMemberProfileField_ReturnsTrue() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "7"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithNonMatchingMemberProfileField_ReturnsFalse() { - private const string DefinitionFormat = "{{ \"alias\": \"{0}\", \"match\": \"{1}\", \"value\": \"{2}\" }}"; - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingProfileField_WithMatchingField_ReturnsTrue() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "abc", "MatchesValue", "xyz"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingProfileField_WithNonMatchingField_ReturnsFalse() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "abc", "MatchesValue", "zyx"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionNotMatchingProfileField_WithMatchingField_ReturnsFalse() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "abc", "DoesNotMatchValue", "xyz"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingProfileField_WithNonMatchingField_ReturnsTrue() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "abc", "DoesNotMatchValue", "zyx"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithMatchingMemberProfileField_ReturnsTrue() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-APR-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithNonMatchingMemberProfileField_ReturnsFalse() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-JUN-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithMatchingMemberProfileField_ReturnsTrue() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "3"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithNonMatchingMemberProfileField_ReturnsFalse() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "7"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithMatchingMemberProfileField_ReturnsTrue() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "aaa"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithNonMatchingMemberProfileField_ReturnsFalse() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "ccc"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithMatchingMemberProfileField_ReturnsTrue() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-JUN-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithNonMatchingMemberProfileField_ReturnsFalse() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-APR-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithMatchingMemberProfileField_ReturnsTrue() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "7"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithNonMatchingMemberProfileField_ReturnsFalse() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "3"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithMatchingMemberProfileField_ReturnsTrue() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "ccc"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithNonMatchingMemberProfileField_ReturnsFalse() - { - // Arrange - var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); - var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "aaa"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - private static Mock MockMemberProfileFieldProvider() - { - var mock = new Mock(); - - mock.Setup(x => x.GetMemberProfileFieldValue(It.Is(y => y == "abc"))).Returns("xyz"); - mock.Setup(x => x.GetMemberProfileFieldValue(It.Is(y => y == "dateCompareTest"))).Returns("1-MAY-2015 10:30:00"); - mock.Setup(x => x.GetMemberProfileFieldValue(It.Is(y => y == "numericCompareTest"))).Returns("5"); - mock.Setup(x => x.GetMemberProfileFieldValue(It.Is(y => y == "stringCompareTest"))).Returns("bbb"); - - return mock; - } + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "3"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithMatchingMemberProfileField_ReturnsTrue() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "ccc"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void MemberProfileFieldPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithNonMatchingMemberProfileField_ReturnsFalse() + { + // Arrange + var mockMemberProfileFieldProvider = MockMemberProfileFieldProvider(); + var criteria = new MemberProfileFieldPersonalisationGroupCriteria(mockMemberProfileFieldProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "aaa"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + private static Mock MockMemberProfileFieldProvider() + { + var mock = new Mock(); + + mock.Setup(x => x.GetMemberProfileFieldValue(It.Is(y => y == "abc"))).Returns("xyz"); + mock.Setup(x => x.GetMemberProfileFieldValue(It.Is(y => y == "dateCompareTest"))).Returns("1-MAY-2015 10:30:00"); + mock.Setup(x => x.GetMemberProfileFieldValue(It.Is(y => y == "numericCompareTest"))).Returns("5"); + mock.Setup(x => x.GetMemberProfileFieldValue(It.Is(y => y == "stringCompareTest"))).Returns("bbb"); + + return mock; } } \ No newline at end of file diff --git a/PersonalisationGroups.Tests/Criteria/MemberType/MemberTypePersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/MemberType/MemberTypePersonalisationGroupCriteriaTests.cs index b90d0dd..a09b083 100644 --- a/PersonalisationGroups.Tests/Criteria/MemberType/MemberTypePersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/MemberType/MemberTypePersonalisationGroupCriteriaTests.cs @@ -1,111 +1,110 @@ using System; using NUnit.Framework; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.MemberType; -using Our.Umbraco.PersonalisationGroups.Core.Providers.MemberType; +using Our.Umbraco.PersonalisationGroups.Criteria.MemberType; +using Our.Umbraco.PersonalisationGroups.Providers.MemberType; -namespace Zone.UmbracoPersonalisationGroups.V8.Tests.Criteria.MemberType +namespace Zone.UmbracoPersonalisationGroups.V8.Tests.Criteria.MemberType; + + +[TestFixture] +public class MemberTypePersonalisationGroupCriteriaTests { + private const string DefinitionFormat = "{{ \"typeName\": \"{0}\", \"match\": \"{1}\" }}"; + + [Test] + public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockMemberTypeProvider = MockMemberTypeProvider(); + var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockMemberTypeProvider = MockMemberTypeProvider(); + var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingType_WithMatchingType_ReturnsTrue() + { + // Arrange + var mockMemberTypeProvider = MockMemberTypeProvider(); + var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); + var definition = string.Format(DefinitionFormat, "Member", "IsOfType"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingType_WithNonMatchingType_ReturnsFalse() + { + // Arrange + var mockMemberTypeProvider = MockMemberTypeProvider("anotherType"); + var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); + var definition = string.Format(DefinitionFormat, "Member", "IsOfType"); - [TestFixture] - public class MemberTypePersonalisationGroupCriteriaTests + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionNotMatchingType_WithMatchingType_ReturnsFalse() { - private const string DefinitionFormat = "{{ \"typeName\": \"{0}\", \"match\": \"{1}\" }}"; - - [Test] - public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockMemberTypeProvider = MockMemberTypeProvider(); - var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockMemberTypeProvider = MockMemberTypeProvider(); - var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingType_WithMatchingType_ReturnsTrue() - { - // Arrange - var mockMemberTypeProvider = MockMemberTypeProvider(); - var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); - var definition = string.Format(DefinitionFormat, "Member", "IsOfType"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchingType_WithNonMatchingType_ReturnsFalse() - { - // Arrange - var mockMemberTypeProvider = MockMemberTypeProvider("anotherType"); - var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); - var definition = string.Format(DefinitionFormat, "Member", "IsOfType"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionNotMatchingType_WithMatchingType_ReturnsFalse() - { - // Arrange - var mockMemberTypeProvider = MockMemberTypeProvider(); - var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); - var definition = string.Format(DefinitionFormat, "Member", "IsNotOfType"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionNotMatchingType_WithNonMatchingType_ReturnsTrue() - { - // Arrange - var mockMemberTypeProvider = MockMemberTypeProvider("anotherType"); - var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); - var definition = string.Format(DefinitionFormat, "Member", "IsNotOfType"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - #region Mocks - - private static Mock MockMemberTypeProvider(string typeName = "member") - { - var mock = new Mock(); - - mock.Setup(x => x.GetMemberType()).Returns(typeName); - - return mock; - } - - #endregion + // Arrange + var mockMemberTypeProvider = MockMemberTypeProvider(); + var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); + var definition = string.Format(DefinitionFormat, "Member", "IsNotOfType"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); } + + [Test] + public void MemberTypePersonalisationGroupCriteria_MatchesVisitor_WithDefinitionNotMatchingType_WithNonMatchingType_ReturnsTrue() + { + // Arrange + var mockMemberTypeProvider = MockMemberTypeProvider("anotherType"); + var criteria = new MemberTypePersonalisationGroupCriteria(mockMemberTypeProvider.Object); + var definition = string.Format(DefinitionFormat, "Member", "IsNotOfType"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + #region Mocks + + private static Mock MockMemberTypeProvider(string typeName = "member") + { + var mock = new Mock(); + + mock.Setup(x => x.GetMemberType()).Returns(typeName); + + return mock; + } + + #endregion } \ No newline at end of file diff --git a/PersonalisationGroups.Tests/Criteria/MonthOfYear/MonthOfYearPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/MonthOfYear/MonthOfYearPersonalisationGroupCriteriaTests.cs index 67d2635..a059d86 100644 --- a/PersonalisationGroups.Tests/Criteria/MonthOfYear/MonthOfYearPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/MonthOfYear/MonthOfYearPersonalisationGroupCriteriaTests.cs @@ -1,80 +1,79 @@ using System; using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.MonthOfYear; +using Our.Umbraco.PersonalisationGroups.Criteria.MonthOfYear; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.MonthOfYear +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.MonthOfYear; + +[TestFixture] +public class MonthOfYearPersonalisationGroupCriteriaTests : DateTimeCriteriaTestsBase { - [TestFixture] - public class MonthOfYearPersonalisationGroupCriteriaTests : DateTimeCriteriaTestsBase - { - private const string DefinitionFormat = "[ {0}, {1} ]"; + private const string DefinitionFormat = "[ {0}, {1} ]"; - [Test] - public void MonthOfYearPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new MonthOfYearPersonalisationGroupCriteria(mockDateTimeProvider.Object); + [Test] + public void MonthOfYearPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new MonthOfYearPersonalisationGroupCriteria(mockDateTimeProvider.Object); - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } - [Test] - public void MonthOfYearPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new MonthOfYearPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = "invalid"; + [Test] + public void MonthOfYearPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new MonthOfYearPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = "invalid"; - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } - [Test] - public void MonthOfYearPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithEmptyDays_ReturnsFalse() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new MonthOfYearPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = "[]"; + [Test] + public void MonthOfYearPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithEmptyDays_ReturnsFalse() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new MonthOfYearPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = "[]"; - // Act - var result = criteria.MatchesVisitor(definition); + // Act + var result = criteria.MatchesVisitor(definition); - // Assert - Assert.IsFalse(result); - } + // Assert + Assert.IsFalse(result); + } - [Test] - public void MonthOfYearPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentDays_ReturnsFalse() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new MonthOfYearPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = string.Format(DefinitionFormat, 2, 3); // February, March + [Test] + public void MonthOfYearPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentDays_ReturnsFalse() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new MonthOfYearPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = string.Format(DefinitionFormat, 2, 3); // February, March - // Act - var result = criteria.MatchesVisitor(definition); + // Act + var result = criteria.MatchesVisitor(definition); - // Assert - Assert.IsFalse(result); - } + // Assert + Assert.IsFalse(result); + } - [Test] - public void MonthOfYearPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingDays_ReturnsTrue() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new MonthOfYearPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = string.Format(DefinitionFormat, 6, 7); // June, July + [Test] + public void MonthOfYearPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingDays_ReturnsTrue() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new MonthOfYearPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = string.Format(DefinitionFormat, 6, 7); // June, July - // Act - var result = criteria.MatchesVisitor(definition); + // Act + var result = criteria.MatchesVisitor(definition); - // Assert - Assert.IsTrue(result); - } + // Assert + Assert.IsTrue(result); } } diff --git a/PersonalisationGroups.Tests/Criteria/NumberOfVisits/NumberOfVisitsPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/NumberOfVisits/NumberOfVisitsPersonalisationGroupCriteriaTests.cs index 51b0931..9a08bb0 100644 --- a/PersonalisationGroups.Tests/Criteria/NumberOfVisits/NumberOfVisitsPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/NumberOfVisits/NumberOfVisitsPersonalisationGroupCriteriaTests.cs @@ -1,136 +1,135 @@ using System; using NUnit.Framework; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.NumberOfVisits; -using Our.Umbraco.PersonalisationGroups.Core.Providers.NumberOfVisits; +using Our.Umbraco.PersonalisationGroups.Criteria.NumberOfVisits; +using Our.Umbraco.PersonalisationGroups.Providers.NumberOfVisits; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.NumberOfVisits +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.NumberOfVisits; + +[TestFixture] +public class NumberOfVisitsPersonalisationGroupCriteriaTests { - [TestFixture] - public class NumberOfVisitsPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"match\": \"{0}\", \"number\": {1} }}"; + + [Test] + public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(); + var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(); + var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForMoreThanNumber_WithMoreThanTheNumberOfVisits_ReturnsTrue() + { + // Arrange + var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(3); + var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); + var definition = string.Format(DefinitionFormat, "MoreThan", 2); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForMoreThanNumber_WithLessThanTheNumberOfVisits_ReturnsFalse() + { + // Arrange + var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(1); + var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); + var definition = string.Format(DefinitionFormat, "MoreThan", 2); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumber_WithLessThanTheNumberOfVisits_ReturnsTrue() + { + // Arrange + var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(1); + var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); + var definition = string.Format(DefinitionFormat, "LessThan", 2); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumber_WithMoreThanTheNumberOfVisits_ReturnsFalse() { - private const string DefinitionFormat = "{{ \"match\": \"{0}\", \"number\": {1} }}"; - - [Test] - public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(); - var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(); - var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForMoreThanNumber_WithMoreThanTheNumberOfVisits_ReturnsTrue() - { - // Arrange - var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(3); - var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); - var definition = string.Format(DefinitionFormat, "MoreThan", 2); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForMoreThanNumber_WithLessThanTheNumberOfVisits_ReturnsFalse() - { - // Arrange - var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(1); - var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); - var definition = string.Format(DefinitionFormat, "MoreThan", 2); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumber_WithLessThanTheNumberOfVisits_ReturnsTrue() - { - // Arrange - var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(1); - var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); - var definition = string.Format(DefinitionFormat, "LessThan", 2); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumber_WithMoreThanTheNumberOfVisits_ReturnsFalse() - { - // Arrange - var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(3); - var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); - var definition = string.Format(DefinitionFormat, "LessThan", 2); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForExactlyNumber_WithExactlyTheNumberOfVisits_ReturnsTrue() - { - // Arrange - var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(2); - var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); - var definition = string.Format(DefinitionFormat, "Exactly", 2); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForExactlyNumber_WithNotExactlyTheNumberOfVisits_ReturnsFalse() - { - // Arrange - var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(3); - var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); - var definition = string.Format(DefinitionFormat, "Exactly", 2); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - private static Mock MockNumberOfVisitsProvider(int result = 0) - { - var mock = new Mock(); - - mock.Setup(x => x.GetNumberOfVisits()).Returns(result); - - return mock; - } + // Arrange + var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(3); + var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); + var definition = string.Format(DefinitionFormat, "LessThan", 2); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForExactlyNumber_WithExactlyTheNumberOfVisits_ReturnsTrue() + { + // Arrange + var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(2); + var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); + var definition = string.Format(DefinitionFormat, "Exactly", 2); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void NumberOfVisitsPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForExactlyNumber_WithNotExactlyTheNumberOfVisits_ReturnsFalse() + { + // Arrange + var mockNumberOfVisitsProvider = MockNumberOfVisitsProvider(3); + var criteria = new NumberOfVisitsPersonalisationGroupCriteria(mockNumberOfVisitsProvider.Object); + var definition = string.Format(DefinitionFormat, "Exactly", 2); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + private static Mock MockNumberOfVisitsProvider(int result = 0) + { + var mock = new Mock(); + + mock.Setup(x => x.GetNumberOfVisits()).Returns(result); + + return mock; } } diff --git a/PersonalisationGroups.Tests/Criteria/PagesViewed/PagesViewedPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/PagesViewed/PagesViewedPersonalisationGroupCriteriaTests.cs index 5435050..69a3e77 100644 --- a/PersonalisationGroups.Tests/Criteria/PagesViewed/PagesViewedPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/PagesViewed/PagesViewedPersonalisationGroupCriteriaTests.cs @@ -1,182 +1,188 @@ using System; using NUnit.Framework; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.PagesViewed; -using Our.Umbraco.PersonalisationGroups.Core.Providers.PagesViewed; +using Our.Umbraco.PersonalisationGroups.Criteria.PagesViewed; +using Our.Umbraco.PersonalisationGroups.Providers.PagesViewed; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.PagesViewed +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.PagesViewed; + +[TestFixture] +public class PagesViewedPersonalisationGroupCriteriaTests { - [TestFixture] - public class PagesViewedPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"match\": \"{0}\", \"nodeKeys\": [{1}] }}"; + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider(); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider(); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesViewedAny_WithPageViewed_ReturnsTrue() + { + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider(); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + var definition = string.Format(DefinitionFormat, "ViewedAny", "'77d47dd7-a0e2-42a3-9103-27d5f9470ce8'"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesViewedAny_WithPageNotViewed_ReturnsFalse() + { + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider(); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + var definition = string.Format(DefinitionFormat, "ViewedAny", "'2be18c6f-fa74-456d-a52c-d683d8d99bf5'"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesViewedAll_WithPagesViewed_ReturnsTrue() + { + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider(); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + var definition = string.Format(DefinitionFormat, "ViewedAll", "'77d47dd7-a0e2-42a3-9103-27d5f9470ce8','6056e478-c614-4cb1-b389-c9fe7e950555','e7a3a2fc-c406-4482-88e7-c39a84f649e7'"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesViewedAll_WithPagesViewedAndMore_ReturnsTrue() + { + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider([ + Guid.Parse("77d47dd7-a0e2-42a3-9103-27d5f9470ce8"), + Guid.Parse("6056e478-c614-4cb1-b389-c9fe7e950555"), + Guid.Parse("e7a3a2fc-c406-4482-88e7-c39a84f649e7"), + Guid.Parse("35be0910-c0ed-4f8f-9752-7639d796c1f7")]); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + var definition = string.Format(DefinitionFormat, "ViewedAll", "'77d47dd7-a0e2-42a3-9103-27d5f9470ce8','6056e478-c614-4cb1-b389-c9fe7e950555','e7a3a2fc-c406-4482-88e7-c39a84f649e7'"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesViewedAll_WithPagesNotViewed_ReturnsFalse() { - private const string DefinitionFormat = "{{ \"match\": \"{0}\", \"nodeIds\": [{1}] }}"; - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesViewedAny_WithPageViewed_ReturnsTrue() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - var definition = string.Format(DefinitionFormat, "ViewedAny", "1000"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesViewedAny_WithPageNotViewed_ReturnsFalse() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - var definition = string.Format(DefinitionFormat, "ViewedAny", "1004"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesViewedAll_WithPagesViewed_ReturnsTrue() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - var definition = string.Format(DefinitionFormat, "ViewedAll", "1001,1000,1002"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesViewedAll_WithPagesViewedAndMore_ReturnsTrue() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(new[] { 1000, 1001, 1002, 1003, 1004 }); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - var definition = string.Format(DefinitionFormat, "ViewedAll", "1001,1000,1002"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesViewedAll_WithPagesNotViewed_ReturnsFalse() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - var definition = string.Format(DefinitionFormat, "ViewedAll", "1000,1003"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesNotViewedAny_WithPageViewed_ReturnsFalse() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - var definition = string.Format(DefinitionFormat, "NotViewedAny", "1000"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesNotViewedAny_WithPageNotViewed_ReturnsTrue() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - var definition = string.Format(DefinitionFormat, "NotViewedAny", "1004"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesNotViewedAll_WithPagesViewed_ReturnsFalse() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - var definition = string.Format(DefinitionFormat, "NotViewedAll", "1001,1000,1002"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesNotViewedAll_WithPagesNotViewed_ReturnsTrue() - { - // Arrange - var mockPagesViewedProvider = MockPagesViewedProvider(); - var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); - var definition = string.Format(DefinitionFormat, "NotViewedAll", "1000,1003"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - private static Mock MockPagesViewedProvider(int[] pagesViewed = null) - { - var mock = new Mock(); - pagesViewed = pagesViewed ?? new[] {1000, 1001, 1002}; - - mock.Setup(x => x.GetNodeIdsViewed()).Returns(pagesViewed); - - return mock; - } + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider(); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + var definition = string.Format(DefinitionFormat, "ViewedAll", "'77d47dd7-a0e2-42a3-9103-27d5f9470ce8','2be18c6f-fa74-456d-a52c-d683d8d99bf5'"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesNotViewedAny_WithPageViewed_ReturnsFalse() + { + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider(); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + var definition = string.Format(DefinitionFormat, "NotViewedAny", "'77d47dd7-a0e2-42a3-9103-27d5f9470ce8'"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesNotViewedAny_WithPageNotViewed_ReturnsTrue() + { + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider(); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + var definition = string.Format(DefinitionFormat, "NotViewedAny", "'2be18c6f-fa74-456d-a52c-d683d8d99bf5'"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesNotViewedAll_WithPagesViewed_ReturnsFalse() + { + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider(); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + var definition = string.Format(DefinitionFormat, "NotViewedAll", "'77d47dd7-a0e2-42a3-9103-27d5f9470ce8','6056e478-c614-4cb1-b389-c9fe7e950555','e7a3a2fc-c406-4482-88e7-c39a84f649e7'"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void PagesViewedPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForPagesNotViewedAll_WithPagesNotViewed_ReturnsTrue() + { + // Arrange + var mockPagesViewedProvider = MockPagesViewedProvider(); + var criteria = new PagesViewedPersonalisationGroupCriteria(mockPagesViewedProvider.Object); + var definition = string.Format(DefinitionFormat, "NotViewedAll", "'77d47dd7-a0e2-42a3-9103-27d5f9470ce8','2be18c6f-fa74-456d-a52c-d683d8d99bf5'"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + private static Mock MockPagesViewedProvider(Guid[]? pagesViewed = null) + { + var mock = new Mock(); + pagesViewed = pagesViewed ?? [ + Guid.Parse("77d47dd7-a0e2-42a3-9103-27d5f9470ce8"), + Guid.Parse("6056e478-c614-4cb1-b389-c9fe7e950555"), + Guid.Parse("e7a3a2fc-c406-4482-88e7-c39a84f649e7")]; + + mock.Setup(x => x.GetNodeKeysViewed()).Returns(pagesViewed); + + return mock; } } diff --git a/PersonalisationGroups.Tests/Criteria/PersonalisationGroupCriteriaBaseTests.cs b/PersonalisationGroups.Tests/Criteria/PersonalisationGroupCriteriaBaseTests.cs index c93d616..dc09d4c 100644 --- a/PersonalisationGroups.Tests/Criteria/PersonalisationGroupCriteriaBaseTests.cs +++ b/PersonalisationGroups.Tests/Criteria/PersonalisationGroupCriteriaBaseTests.cs @@ -1,155 +1,154 @@ using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core; -using Our.Umbraco.PersonalisationGroups.Core.Criteria; +using Our.Umbraco.PersonalisationGroups; +using Our.Umbraco.PersonalisationGroups.Criteria; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria; + +[TestFixture] +public class PersonalisationGroupCriteriaBaseTests { - [TestFixture] - public class PersonalisationGroupCriteriaBaseTests - { - private StubPersonalisationGroupCriteria _criteria; + private StubPersonalisationGroupCriteria _criteria = null!; - private class StubPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase + private class StubPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase + { + public bool TestCompareValues(string value, string definitionValue, Comparison comparison) { - public bool TestCompareValues(string value, string definitionValue, Comparison comparison) - { - return CompareValues(value, definitionValue, comparison); - } + return CompareValues(value, definitionValue, comparison); } + } - [SetUp] - public void Initialize() - { - _criteria = new StubPersonalisationGroupCriteria(); - } + [SetUp] + public void Initialize() + { + _criteria = new StubPersonalisationGroupCriteria(); + } - [Test] - public void PersonalisationGroupCriteriaBase_TestCompareValues_DateComparisons_ReturnsCorrectValues() - { - // Arrange - const string value1 = "1-APR-2015"; - const string value2 = "1-MAY-2015"; - const string value3 = "1-JUN-2015"; - - // Act - var result = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThan); - var result2 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThan); - var result3 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThan); - - var result4 = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThanOrEqual); - var result5 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThanOrEqual); - var result6 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThanOrEqual); - - var result7 = _criteria.TestCompareValues(value2, value1, Comparison.LessThan); - var result8 = _criteria.TestCompareValues(value2, value2, Comparison.LessThan); - var result9 = _criteria.TestCompareValues(value2, value3, Comparison.LessThan); - - var result10 = _criteria.TestCompareValues(value2, value1, Comparison.LessThanOrEqual); - var result11 = _criteria.TestCompareValues(value2, value2, Comparison.LessThanOrEqual); - var result12 = _criteria.TestCompareValues(value2, value3, Comparison.LessThanOrEqual); - - // Assert - Assert.IsTrue(result); - Assert.IsFalse(result2); - Assert.IsFalse(result3); - - Assert.IsTrue(result4); - Assert.IsTrue(result5); - Assert.IsFalse(result6); - - Assert.IsFalse(result7); - Assert.IsFalse(result8); - Assert.IsTrue(result9); - - Assert.IsFalse(result10); - Assert.IsTrue(result11); - Assert.IsTrue(result12); - } + [Test] + public void PersonalisationGroupCriteriaBase_TestCompareValues_DateComparisons_ReturnsCorrectValues() + { + // Arrange + const string value1 = "1-APR-2015"; + const string value2 = "1-MAY-2015"; + const string value3 = "1-JUN-2015"; + + // Act + var result = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThan); + var result2 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThan); + var result3 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThan); + + var result4 = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThanOrEqual); + var result5 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThanOrEqual); + var result6 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThanOrEqual); + + var result7 = _criteria.TestCompareValues(value2, value1, Comparison.LessThan); + var result8 = _criteria.TestCompareValues(value2, value2, Comparison.LessThan); + var result9 = _criteria.TestCompareValues(value2, value3, Comparison.LessThan); + + var result10 = _criteria.TestCompareValues(value2, value1, Comparison.LessThanOrEqual); + var result11 = _criteria.TestCompareValues(value2, value2, Comparison.LessThanOrEqual); + var result12 = _criteria.TestCompareValues(value2, value3, Comparison.LessThanOrEqual); + + // Assert + Assert.IsTrue(result); + Assert.IsFalse(result2); + Assert.IsFalse(result3); + + Assert.IsTrue(result4); + Assert.IsTrue(result5); + Assert.IsFalse(result6); + + Assert.IsFalse(result7); + Assert.IsFalse(result8); + Assert.IsTrue(result9); + + Assert.IsFalse(result10); + Assert.IsTrue(result11); + Assert.IsTrue(result12); + } - [Test] - public void PersonalisationGroupCriteriaBase_TestCompareValues_NumericComparisons_ReturnsCorrectValues() - { - // Arrange - const string value1 = "1.1"; - const string value2 = "2.2"; - const string value3 = "3.3"; - - // Act - var result = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThan); - var result2 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThan); - var result3 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThan); - - var result4 = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThanOrEqual); - var result5 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThanOrEqual); - var result6 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThanOrEqual); - - var result7 = _criteria.TestCompareValues(value2, value1, Comparison.LessThan); - var result8 = _criteria.TestCompareValues(value2, value2, Comparison.LessThan); - var result9 = _criteria.TestCompareValues(value2, value3, Comparison.LessThan); - - var result10 = _criteria.TestCompareValues(value2, value1, Comparison.LessThanOrEqual); - var result11 = _criteria.TestCompareValues(value2, value2, Comparison.LessThanOrEqual); - var result12 = _criteria.TestCompareValues(value2, value3, Comparison.LessThanOrEqual); - - // Assert - Assert.IsTrue(result); - Assert.IsFalse(result2); - Assert.IsFalse(result3); - - Assert.IsTrue(result4); - Assert.IsTrue(result5); - Assert.IsFalse(result6); - - Assert.IsFalse(result7); - Assert.IsFalse(result8); - Assert.IsTrue(result9); - - Assert.IsFalse(result10); - Assert.IsTrue(result11); - Assert.IsTrue(result12); - } + [Test] + public void PersonalisationGroupCriteriaBase_TestCompareValues_NumericComparisons_ReturnsCorrectValues() + { + // Arrange + const string value1 = "1.1"; + const string value2 = "2.2"; + const string value3 = "3.3"; + + // Act + var result = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThan); + var result2 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThan); + var result3 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThan); + + var result4 = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThanOrEqual); + var result5 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThanOrEqual); + var result6 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThanOrEqual); + + var result7 = _criteria.TestCompareValues(value2, value1, Comparison.LessThan); + var result8 = _criteria.TestCompareValues(value2, value2, Comparison.LessThan); + var result9 = _criteria.TestCompareValues(value2, value3, Comparison.LessThan); + + var result10 = _criteria.TestCompareValues(value2, value1, Comparison.LessThanOrEqual); + var result11 = _criteria.TestCompareValues(value2, value2, Comparison.LessThanOrEqual); + var result12 = _criteria.TestCompareValues(value2, value3, Comparison.LessThanOrEqual); + + // Assert + Assert.IsTrue(result); + Assert.IsFalse(result2); + Assert.IsFalse(result3); + + Assert.IsTrue(result4); + Assert.IsTrue(result5); + Assert.IsFalse(result6); + + Assert.IsFalse(result7); + Assert.IsFalse(result8); + Assert.IsTrue(result9); + + Assert.IsFalse(result10); + Assert.IsTrue(result11); + Assert.IsTrue(result12); + } - [Test] - public void PersonalisationGroupCriteriaBase_TestCompareValues_StringComparisons_ReturnsCorrectValues() - { - // Arrange - const string value1 = "a"; - const string value2 = "b"; - const string value3 = "c"; - - // Act - var result = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThan); - var result2 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThan); - var result3 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThan); - - var result4 = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThanOrEqual); - var result5 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThanOrEqual); - var result6 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThanOrEqual); - - var result7 = _criteria.TestCompareValues(value2, value1, Comparison.LessThan); - var result8 = _criteria.TestCompareValues(value2, value2, Comparison.LessThan); - var result9 = _criteria.TestCompareValues(value2, value3, Comparison.LessThan); - - var result10 = _criteria.TestCompareValues(value2, value1, Comparison.LessThanOrEqual); - var result11 = _criteria.TestCompareValues(value2, value2, Comparison.LessThanOrEqual); - var result12 = _criteria.TestCompareValues(value2, value3, Comparison.LessThanOrEqual); - - // Assert - Assert.IsTrue(result); - Assert.IsFalse(result2); - Assert.IsFalse(result3); - - Assert.IsTrue(result4); - Assert.IsTrue(result5); - Assert.IsFalse(result6); - - Assert.IsFalse(result7); - Assert.IsFalse(result8); - Assert.IsTrue(result9); - - Assert.IsFalse(result10); - Assert.IsTrue(result11); - Assert.IsTrue(result12); - } + [Test] + public void PersonalisationGroupCriteriaBase_TestCompareValues_StringComparisons_ReturnsCorrectValues() + { + // Arrange + const string value1 = "a"; + const string value2 = "b"; + const string value3 = "c"; + + // Act + var result = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThan); + var result2 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThan); + var result3 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThan); + + var result4 = _criteria.TestCompareValues(value2, value1, Comparison.GreaterThanOrEqual); + var result5 = _criteria.TestCompareValues(value2, value2, Comparison.GreaterThanOrEqual); + var result6 = _criteria.TestCompareValues(value2, value3, Comparison.GreaterThanOrEqual); + + var result7 = _criteria.TestCompareValues(value2, value1, Comparison.LessThan); + var result8 = _criteria.TestCompareValues(value2, value2, Comparison.LessThan); + var result9 = _criteria.TestCompareValues(value2, value3, Comparison.LessThan); + + var result10 = _criteria.TestCompareValues(value2, value1, Comparison.LessThanOrEqual); + var result11 = _criteria.TestCompareValues(value2, value2, Comparison.LessThanOrEqual); + var result12 = _criteria.TestCompareValues(value2, value3, Comparison.LessThanOrEqual); + + // Assert + Assert.IsTrue(result); + Assert.IsFalse(result2); + Assert.IsFalse(result3); + + Assert.IsTrue(result4); + Assert.IsTrue(result5); + Assert.IsFalse(result6); + + Assert.IsFalse(result7); + Assert.IsFalse(result8); + Assert.IsTrue(result9); + + Assert.IsFalse(result10); + Assert.IsTrue(result11); + Assert.IsTrue(result12); } } diff --git a/PersonalisationGroups.Tests/Criteria/Querystring/QuerystringPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/Querystring/QuerystringPersonalisationGroupCriteriaTests.cs index e42571f..c9fd2c3 100644 --- a/PersonalisationGroups.Tests/Criteria/Querystring/QuerystringPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/Querystring/QuerystringPersonalisationGroupCriteriaTests.cs @@ -2,357 +2,356 @@ using Microsoft.Extensions.Primitives; using Moq; using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.Querystring; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Querystring; +using Our.Umbraco.PersonalisationGroups.Criteria.Querystring; +using Our.Umbraco.PersonalisationGroups.Providers.Querystring; using System; using System.Collections.Generic; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Querystring +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Querystring; + +[TestFixture] +public class QuerystringPersonalisationGroupCriteriaTests { - [TestFixture] - public class QuerystringPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"key\": \"{0}\", \"match\": \"{1}\", \"value\": \"{2}\" }}"; + + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() { - private const string DefinitionFormat = "{{ \"key\": \"{0}\", \"match\": \"{1}\", \"value\": \"{2}\" }}"; + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = "invalid"; - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = "invalid"; + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForQuerystringMatchingValue_WithMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,ccc"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForQuerystringMatchingValue_WithMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,ccc"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.IsTrue(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForQuerystringMatchingValue_WithNonMatchingQuerystring_ReturnsFalse() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,xxx"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForQuerystringMatchingValue_WithNonMatchingQuerystring_ReturnsFalse() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,xxx"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsFalse(result); + } - // Assert - Assert.IsFalse(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForQuerystringContainingValue_WithMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "bbb"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForQuerystringContainingValue_WithMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "bbb"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.IsTrue(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForQuerystringContainingValue_WithNonMatchingQuerystring_ReturnsFalse() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "xxx"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForQuerystringContainingValue_WithNonMatchingQuerystring_ReturnsFalse() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "xxx"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsFalse(result); + } - // Assert - Assert.IsFalse(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-APR-2015"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-APR-2015"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.IsTrue(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithNonMatchingQuerystring_ReturnsFalse() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-JUN-2015"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithNonMatchingQuerystring_ReturnsFalse() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-JUN-2015"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsFalse(result); + } - // Assert - Assert.IsFalse(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "3"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "3"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.IsTrue(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithNonMatchingQuerystring_ReturnsFalse() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "7"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithNonMatchingQuerystring_ReturnsFalse() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "7"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsFalse(result); + } - // Assert - Assert.IsFalse(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "aaa"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "aaa"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.IsTrue(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithNonMatchingQuerystring_ReturnsFalse() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "ccc"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithNonMatchingQuerystring_ReturnsFalse() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "ccc"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsFalse(result); + } - // Assert - Assert.IsFalse(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-JUN-2015"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-JUN-2015"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.IsTrue(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithNonMatchingQuerystring_ReturnsFalse() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-APR-2015"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithNonMatchingQuerystring_ReturnsFalse() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-APR-2015"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsFalse(result); + } - // Assert - Assert.IsFalse(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "7"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "7"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.IsTrue(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithNonMatchingQuerystring_ReturnsFalse() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "3"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithNonMatchingQuerystring_ReturnsFalse() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "3"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsFalse(result); + } - // Assert - Assert.IsFalse(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "ccc"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "ccc"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.IsTrue(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithNonMatchingQuerystring_ReturnsFalse() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "aaa"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithNonMatchingQuerystring_ReturnsFalse() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "aaa"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsFalse(result); + } - // Assert - Assert.IsFalse(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[a-z]"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[a-z]"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.IsTrue(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithNonMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[A-Z]"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithNonMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[A-Z]"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsFalse(result); + } - // Assert - Assert.IsFalse(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithMatchingQuerystring_ReturnsFalse() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[a-z]"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithMatchingQuerystring_ReturnsFalse() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[a-z]"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsFalse(result); + } - // Assert - Assert.IsFalse(result); - } + [Test] + public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithNonMatchingQuerystring_ReturnsTrue() + { + // Arrange + var mockQuerystringProvider = MockQuerystringProvider(); + var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[A-Z]"); - [Test] - public void QuerystringPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithNonMatchingQuerystring_ReturnsTrue() - { - // Arrange - var mockQuerystringProvider = MockQuerystringProvider(); - var criteria = new QuerystringPersonalisationGroupCriteria(mockQuerystringProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[A-Z]"); + // Act + var result = criteria.MatchesVisitor(definition); - // Act - var result = criteria.MatchesVisitor(definition); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.IsTrue(result); - } + private static Mock MockQuerystringProvider() + { + var mock = new Mock(); - private static Mock MockQuerystringProvider() + var queryCollectionValues = new Dictionary { - var mock = new Mock(); - - var queryCollectionValues = new Dictionary - { - { "key", new StringValues("aaa,bbb,ccc") }, - { "dateCompareTest", new StringValues("1-MAY-2015 10:30:00") }, - { "numericCompareTest", new StringValues("5") }, - { "stringCompareTest", new StringValues("bbb") }, - { "regexTest", new StringValues("b") } - }; - var queryCollection = new QueryCollection(queryCollectionValues); - mock.Setup(x => x.GetQuerystring()).Returns(queryCollection); - - return mock; - } + { "key", new StringValues("aaa,bbb,ccc") }, + { "dateCompareTest", new StringValues("1-MAY-2015 10:30:00") }, + { "numericCompareTest", new StringValues("5") }, + { "stringCompareTest", new StringValues("bbb") }, + { "regexTest", new StringValues("b") } + }; + var queryCollection = new QueryCollection(queryCollectionValues); + mock.Setup(x => x.GetQuerystring()).Returns(queryCollection); + + return mock; } } diff --git a/PersonalisationGroups.Tests/Criteria/Referral/ReferralPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/Referral/ReferralPersonalisationGroupCriteriaTests.cs index 88e7f04..860be3e 100644 --- a/PersonalisationGroups.Tests/Criteria/Referral/ReferralPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/Referral/ReferralPersonalisationGroupCriteriaTests.cs @@ -1,166 +1,165 @@ using System; using NUnit.Framework; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.Referral; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Referrer; +using Our.Umbraco.PersonalisationGroups.Criteria.Referral; +using Our.Umbraco.PersonalisationGroups.Providers.Referrer; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Referral +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Referral; + +[TestFixture] +public class ReferralPersonalisationGroupCriteriaTests { - [TestFixture] - public class ReferralPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"value\": \"{0}\", \"match\": \"{1}\" }}"; + + [Test] + public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockReferralProvider = MockReferralProvider(); + var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockReferralProvider = MockReferralProvider(); + var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForReferrerMatches_WithMatchingValue_ReturnsTrue() + { + // Arrange + var mockReferralProvider = MockReferralProvider(); + var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); + var definition = string.Format(DefinitionFormat, "http://www.example.com/", "MatchesValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForReferrerMatches_WithNonMatchingValue_ReturnsFalse() + { + // Arrange + var mockReferralProvider = MockReferralProvider(); + var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); + var definition = string.Format(DefinitionFormat, "http://www.another-example.com/", "MatchesValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForReferrerDoesNotMatch_WithNonMatchingValue_ReturnsTrue() { - private const string DefinitionFormat = "{{ \"value\": \"{0}\", \"match\": \"{1}\" }}"; - - [Test] - public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockReferralProvider = MockReferralProvider(); - var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockReferralProvider = MockReferralProvider(); - var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForReferrerMatches_WithMatchingValue_ReturnsTrue() - { - // Arrange - var mockReferralProvider = MockReferralProvider(); - var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); - var definition = string.Format(DefinitionFormat, "http://www.example.com/", "MatchesValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForReferrerMatches_WithNonMatchingValue_ReturnsFalse() - { - // Arrange - var mockReferralProvider = MockReferralProvider(); - var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); - var definition = string.Format(DefinitionFormat, "http://www.another-example.com/", "MatchesValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForReferrerDoesNotMatch_WithNonMatchingValue_ReturnsTrue() - { - // Arrange - var mockReferralProvider = MockReferralProvider(); - var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); - var definition = string.Format(DefinitionFormat, "http://www.another-example.com/", "DoesNotMatchValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForReferrerDoesNotMatch_WithMatchingValue_ReturnsFalse() - { - // Arrange - var mockReferralProvider = MockReferralProvider(); - var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); - var definition = string.Format(DefinitionFormat, "http://www.example.com/", "DoesNotMatchValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void ReferralPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForReferrerContains_WithMatchingValue_ReturnsTrue() - { - // Arrange - var mockReferralProvider = MockReferralProvider(); - var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); - var definition = string.Format(DefinitionFormat, "example", "ContainsValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void ReferralPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForReferrerContains_WithNonMatchingValue_ReturnsFalse() - { - // Arrange - var mockReferralProvider = MockReferralProvider(); - var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); - var definition = string.Format(DefinitionFormat, "another-example", "ContainsValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void ReferralPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForReferrerDoesNotMatch_WithNonMatchingValue_ReturnsTrue() - { - // Arrange - var mockReferralProvider = MockReferralProvider(); - var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); - var definition = string.Format(DefinitionFormat, "another-example", "DoesNotContainValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void ReferralPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForReferrerDoesNotMatch_WithMatchingValue_ReturnsFalse() - { - // Arrange - var mockReferralProvider = MockReferralProvider(); - var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); - var definition = string.Format(DefinitionFormat, "example", "DoesNotContainValue"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - private static Mock MockReferralProvider() - { - var mock = new Mock(); - - mock.Setup(x => x.GetReferrer()).Returns("http://www.example.com/"); - - return mock; - } + // Arrange + var mockReferralProvider = MockReferralProvider(); + var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); + var definition = string.Format(DefinitionFormat, "http://www.another-example.com/", "DoesNotMatchValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void ReferralPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForReferrerDoesNotMatch_WithMatchingValue_ReturnsFalse() + { + // Arrange + var mockReferralProvider = MockReferralProvider(); + var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); + var definition = string.Format(DefinitionFormat, "http://www.example.com/", "DoesNotMatchValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void ReferralPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForReferrerContains_WithMatchingValue_ReturnsTrue() + { + // Arrange + var mockReferralProvider = MockReferralProvider(); + var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); + var definition = string.Format(DefinitionFormat, "example", "ContainsValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void ReferralPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForReferrerContains_WithNonMatchingValue_ReturnsFalse() + { + // Arrange + var mockReferralProvider = MockReferralProvider(); + var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); + var definition = string.Format(DefinitionFormat, "another-example", "ContainsValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void ReferralPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForReferrerDoesNotMatch_WithNonMatchingValue_ReturnsTrue() + { + // Arrange + var mockReferralProvider = MockReferralProvider(); + var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); + var definition = string.Format(DefinitionFormat, "another-example", "DoesNotContainValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void ReferralPersonalisationGroupCriteria_ContainsVisitor_WithDefinitionForReferrerDoesNotMatch_WithMatchingValue_ReturnsFalse() + { + // Arrange + var mockReferralProvider = MockReferralProvider(); + var criteria = new ReferralPersonalisationGroupCriteria(mockReferralProvider.Object); + var definition = string.Format(DefinitionFormat, "example", "DoesNotContainValue"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + private static Mock MockReferralProvider() + { + var mock = new Mock(); + + mock.Setup(x => x.GetReferrer()).Returns("http://www.example.com/"); + + return mock; } } diff --git a/PersonalisationGroups.Tests/Criteria/Region/RegionPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/Region/RegionPersonalisationGroupCriteriaTests.cs index c63abd3..2192be9 100644 --- a/PersonalisationGroups.Tests/Criteria/Region/RegionPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/Region/RegionPersonalisationGroupCriteriaTests.cs @@ -1,206 +1,205 @@ using Moq; using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.Region; -using Our.Umbraco.PersonalisationGroups.Core.Providers.GeoLocation; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Ip; +using Our.Umbraco.PersonalisationGroups.Criteria.Region; +using Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; +using Our.Umbraco.PersonalisationGroups.Providers.Ip; using System; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Region +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Region; + +[TestFixture] +public class RegionPersonalisationGroupCriteriaTests { - [TestFixture] - public class RegionPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"match\": \"{0}\", \"countryCode\": \"{1}\", \"names\": [ \"{2}\", \"{3}\" ] }}"; + + [Test] + public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithEmptyRegionLists_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var definition = "{ \"match\": \"IsLocatedIn\", \"countryCode\": \"GB\", \"names\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentRegionList_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var definition = string.Format(DefinitionFormat, "IsLocatedIn", "GB", "Devon", "Somerset"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingRegionList_ReturnsTrue() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var definition = string.Format(DefinitionFormat, "IsLocatedIn", "GB", "Cornwall", "Devon"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingRegionListFromSubdivision_ReturnsTrue() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var definition = string.Format(DefinitionFormat, "IsLocatedIn", "GB", "South-west", "Cumbria"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentRegionListAndNotInCheck_ReturnsTrue() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "GB", "Devon", "Somerset"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingRegionListAndNotInCheck_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "GB", "Cornwall", "Devon"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCannotLocate_ReturnsTrue() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(canGeolocate: false); + var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCanLocate_ReturnsFalse() + { + // Arrange + var mockIpProvider = MockIpProvider(); + var mockCountryGeoLocationProvider = MockGeoLocationProvider(); + var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); + var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + #region Mocks + + private static Mock MockIpProvider() { - private const string DefinitionFormat = "{{ \"match\": \"{0}\", \"countryCode\": \"{1}\", \"names\": [ \"{2}\", \"{3}\" ] }}"; - - [Test] - public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithEmptyRegionLists_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var definition = "{ \"match\": \"IsLocatedIn\", \"countryCode\": \"GB\", \"names\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentRegionList_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var definition = string.Format(DefinitionFormat, "IsLocatedIn", "GB", "Devon", "Somerset"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingRegionList_ReturnsTrue() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var definition = string.Format(DefinitionFormat, "IsLocatedIn", "GB", "Cornwall", "Devon"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingRegionListFromSubdivision_ReturnsTrue() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var definition = string.Format(DefinitionFormat, "IsLocatedIn", "GB", "South-west", "Cumbria"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentRegionListAndNotInCheck_ReturnsTrue() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "GB", "Devon", "Somerset"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingRegionListAndNotInCheck_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var definition = string.Format(DefinitionFormat, "IsNotLocatedIn", "GB", "Cornwall", "Devon"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCannotLocate_ReturnsTrue() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(canGeolocate: false); - var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void RegionPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionForCouldNotBeLocatedWhenCanLocate_ReturnsFalse() - { - // Arrange - var mockIpProvider = MockIpProvider(); - var mockCountryGeoLocationProvider = MockGeoLocationProvider(); - var criteria = new RegionPersonalisationGroupCriteria(mockIpProvider.Object, mockCountryGeoLocationProvider.Object); - var definition = "{ \"match\": \"CouldNotBeLocated\", \"codes\": [] }"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - #region Mocks - - private static Mock MockIpProvider() - { - var mock = new Mock(); - - mock.Setup(x => x.GetIp()).Returns("1.2.3.4"); - - return mock; - } - - private static Mock MockGeoLocationProvider(bool canGeolocate = true) - { - var mock = new Mock(); - - mock.Setup(x => x.GetCountryFromIp(It.IsAny())) - .Returns(canGeolocate - ? new Core.Providers.GeoLocation.Country - { - Code = "GB", - Name = "United Kingdom" - } - : null); - mock.Setup(x => x.GetRegionFromIp(It.IsAny())) - .Returns(canGeolocate - ? new Core.Providers.GeoLocation.Region - { - City = "Cornwall", - Subdivisions = new[] { "South-west"}, - Country = new Core.Providers.GeoLocation.Country { Code = "GB", Name = "United Kingdom" } - } - : null); - - return mock; - } - - #endregion + var mock = new Mock(); + + mock.Setup(x => x.GetIp()).Returns("1.2.3.4"); + + return mock; } + + private static Mock MockGeoLocationProvider(bool canGeolocate = true) + { + var mock = new Mock(); + + mock.Setup(x => x.GetCountryFromIp(It.IsAny())) + .Returns(canGeolocate + ? new PersonalisationGroups.Providers.GeoLocation.Country + { + Code = "GB", + Name = "United Kingdom" + } + : null); + mock.Setup(x => x.GetRegionFromIp(It.IsAny())) + .Returns(canGeolocate + ? new PersonalisationGroups.Providers.GeoLocation.Region + { + City = "Cornwall", + Subdivisions = new[] { "South-west"}, + Country = new PersonalisationGroups.Providers.GeoLocation.Country { Code = "GB", Name = "United Kingdom" } + } + : null); + + return mock; + } + + #endregion } diff --git a/PersonalisationGroups.Tests/Criteria/Session/SessionPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/Session/SessionPersonalisationGroupCriteriaTests.cs index 9288283..8c0399e 100644 --- a/PersonalisationGroups.Tests/Criteria/Session/SessionPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/Session/SessionPersonalisationGroupCriteriaTests.cs @@ -1,431 +1,430 @@ using System; using NUnit.Framework; using Moq; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.Session; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Session; +using Our.Umbraco.PersonalisationGroups.Criteria.Session; +using Our.Umbraco.PersonalisationGroups.Providers.Session; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Session +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.Session; + +[TestFixture] +public class SessionPersonalisationGroupCriteriaTests { - [TestFixture] - public class SessionPersonalisationGroupCriteriaTests + private const string DefinitionFormat = "{{ \"key\": \"{0}\", \"match\": \"{1}\", \"value\": \"{2}\" }}"; + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionExists_WithExistingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "Exists", string.Empty); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionExists_WithMissingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "missing-key", "Exists", string.Empty); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionAbsent_WithAbsentSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "missing-key", "DoesNotExist", string.Empty); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionAbsent_WithExistingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "DoesNotExist", string.Empty); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionMatchingValue_WithMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,ccc"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionMatchingValue_WithNonMatchingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,xxx"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionContainingValue_WithMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "bbb"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionContainingValue_WithNonMatchingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "xxx"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-APR-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithNonMatchingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-JUN-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "3"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithNonMatchingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "7"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "aaa"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithNonMatchingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "ccc"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-JUN-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithNonMatchingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-APR-2015"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "7"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithNonMatchingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "3"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "ccc"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithNonMatchingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "aaa"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[a-z]"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithNonMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[A-Z]"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithMissingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "missing-key", "DoesNotMatchRegex", "[a-z]"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithMatchingSession_ReturnsFalse() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[a-z]"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithNonMatchingSession_ReturnsTrue() + { + // Arrange + var mockSessionProvider = MockSessionProvider(); + var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); + var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[A-Z]"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + private static Mock MockSessionProvider() { - private const string DefinitionFormat = "{{ \"key\": \"{0}\", \"match\": \"{1}\", \"value\": \"{2}\" }}"; - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionExists_WithExistingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "Exists", string.Empty); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionExists_WithMissingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "missing-key", "Exists", string.Empty); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionAbsent_WithAbsentSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "missing-key", "DoesNotExist", string.Empty); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionAbsent_WithExistingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "DoesNotExist", string.Empty); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionMatchingValue_WithMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,ccc"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionMatchingValue_WithNonMatchingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "MatchesValue", "aaa,bbb,xxx"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionContainingValue_WithMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "bbb"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForSessionContainingValue_WithNonMatchingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "key", "ContainsValue", "xxx"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-APR-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanDateValue_WithNonMatchingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "GreaterThanValue", "1-JUN-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "3"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanNumericValue_WithNonMatchingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "GreaterThanValue", "7"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "aaa"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForGreaterThanStringValue_WithNonMatchingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "GreaterThanValue", "ccc"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-JUN-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanDateValue_WithNonMatchingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "dateCompareTest", "LessThanValue", "1-APR-2015"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "7"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanNumericValue_WithNonMatchingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "numericCompareTest", "LessThanValue", "3"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "ccc"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionForLessThanStringValue_WithNonMatchingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "stringCompareTest", "LessThanValue", "aaa"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[a-z]"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionMatchesRegex_WithNonMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "MatchesRegex", "[A-Z]"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithMissingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "missing-key", "DoesNotMatchRegex", "[a-z]"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithMatchingSession_ReturnsFalse() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[a-z]"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void SessionPersonalisationGroupCriteria_MatchesVisitor_WithDefinitionDoesNotMatchRegex_WithNonMatchingSession_ReturnsTrue() - { - // Arrange - var mockSessionProvider = MockSessionProvider(); - var criteria = new SessionPersonalisationGroupCriteria(mockSessionProvider.Object); - var definition = string.Format(DefinitionFormat, "regexTest", "DoesNotMatchRegex", "[A-Z]"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - private static Mock MockSessionProvider() - { - var mock = new Mock(); - - mock.Setup(x => x.KeyExists(It.Is(y => y == "key"))).Returns(true); - mock.Setup(x => x.KeyExists(It.Is(y => y == "dateCompareTest"))).Returns(true); - mock.Setup(x => x.KeyExists(It.Is(y => y == "numericCompareTest"))).Returns(true); - mock.Setup(x => x.KeyExists(It.Is(y => y == "stringCompareTest"))).Returns(true); - mock.Setup(x => x.KeyExists(It.Is(y => y == "missing-key"))).Returns(false); - mock.Setup(x => x.KeyExists(It.Is(y => y == "regexTest"))).Returns(true); - mock.Setup(x => x.GetValue(It.Is(y => y == "key"))).Returns("aaa,bbb,ccc"); - mock.Setup(x => x.GetValue(It.Is(y => y == "dateCompareTest"))).Returns("1-MAY-2015 10:30:00"); - mock.Setup(x => x.GetValue(It.Is(y => y == "numericCompareTest"))).Returns("5"); - mock.Setup(x => x.GetValue(It.Is(y => y == "stringCompareTest"))).Returns("bbb"); - mock.Setup(x => x.GetValue(It.Is(y => y == "regexTest"))).Returns("b"); - - return mock; - } + var mock = new Mock(); + + mock.Setup(x => x.KeyExists(It.Is(y => y == "key"))).Returns(true); + mock.Setup(x => x.KeyExists(It.Is(y => y == "dateCompareTest"))).Returns(true); + mock.Setup(x => x.KeyExists(It.Is(y => y == "numericCompareTest"))).Returns(true); + mock.Setup(x => x.KeyExists(It.Is(y => y == "stringCompareTest"))).Returns(true); + mock.Setup(x => x.KeyExists(It.Is(y => y == "missing-key"))).Returns(false); + mock.Setup(x => x.KeyExists(It.Is(y => y == "regexTest"))).Returns(true); + mock.Setup(x => x.GetValue(It.Is(y => y == "key"))).Returns("aaa,bbb,ccc"); + mock.Setup(x => x.GetValue(It.Is(y => y == "dateCompareTest"))).Returns("1-MAY-2015 10:30:00"); + mock.Setup(x => x.GetValue(It.Is(y => y == "numericCompareTest"))).Returns("5"); + mock.Setup(x => x.GetValue(It.Is(y => y == "stringCompareTest"))).Returns("bbb"); + mock.Setup(x => x.GetValue(It.Is(y => y == "regexTest"))).Returns("b"); + + return mock; } } diff --git a/PersonalisationGroups.Tests/Criteria/TimeOfDay/TimeOfDayPersonalisationGroupCriteriaTests.cs b/PersonalisationGroups.Tests/Criteria/TimeOfDay/TimeOfDayPersonalisationGroupCriteriaTests.cs index 2b9ac5a..71b4ac8 100644 --- a/PersonalisationGroups.Tests/Criteria/TimeOfDay/TimeOfDayPersonalisationGroupCriteriaTests.cs +++ b/PersonalisationGroups.Tests/Criteria/TimeOfDay/TimeOfDayPersonalisationGroupCriteriaTests.cs @@ -1,95 +1,94 @@ using System; using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.TimeOfDay; +using Our.Umbraco.PersonalisationGroups.Criteria.TimeOfDay; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.TimeOfDay +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.TimeOfDay; + +[TestFixture] +public class TimeOfDayPersonalisationGroupCriteriaTests : DateTimeCriteriaTestsBase { - [TestFixture] - public class TimeOfDayPersonalisationGroupCriteriaTests : DateTimeCriteriaTestsBase + private const string DefinitionFormat = "[ {{ \"from\": \"{0}\", \"to\": \"{1}\" }}, {{ \"from\": \"{2}\", \"to\": \"{3}\" }} ]"; + + [Test] + public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); + + // Act + Assert.Throws(() => criteria.MatchesVisitor(string.Empty)); + } + + [Test] + public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = "invalid"; + + // Act + Assert.Throws(() => criteria.MatchesVisitor(definition)); + } + + [Test] + public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithEmptyDays_ReturnsFalse() { - private const string DefinitionFormat = "[ {{ \"from\": \"{0}\", \"to\": \"{1}\" }}, {{ \"from\": \"{2}\", \"to\": \"{3}\" }} ]"; - - [Test] - public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithEmptyDefinition_ThrowsException() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); - - // Act - Assert.Throws(() => criteria.MatchesVisitor(null)); - } - - [Test] - public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithInvalidDefinition_ThrowsException() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = "invalid"; - - // Act - Assert.Throws(() => criteria.MatchesVisitor(definition)); - } - - [Test] - public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithEmptyDays_ReturnsFalse() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = "[]"; - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentTimePeriods_ReturnsFalse() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = string.Format(DefinitionFormat, "0900", "0930", "1100", "1130"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsFalse(result); - } - - [Test] - public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingTimePeriods_ReturnsTrue() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(); - var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = string.Format(DefinitionFormat, "0900", "1015", "1100", "1130"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } - - [Test] - public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingTimePeriodsInLastMinute_ReturnsTrue() - { - // Arrange - var mockDateTimeProvider = MockDateTimeProvider(new DateTime(2016, 1, 1, 23, 59, 59)); - var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); - var definition = string.Format(DefinitionFormat, "0900", "0930", "2300", "2359"); - - // Act - var result = criteria.MatchesVisitor(definition); - - // Assert - Assert.IsTrue(result); - } + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = "[]"; + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithDifferentTimePeriods_ReturnsFalse() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = string.Format(DefinitionFormat, "0900", "0930", "1100", "1130"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsFalse(result); + } + + [Test] + public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingTimePeriods_ReturnsTrue() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(); + var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = string.Format(DefinitionFormat, "0900", "1015", "1100", "1130"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); + } + + [Test] + public void TimeOfDayPersonalisationGroupCriteria_MatchesVisitor_WithValidDefinitionWithMatchingTimePeriodsInLastMinute_ReturnsTrue() + { + // Arrange + var mockDateTimeProvider = MockDateTimeProvider(new DateTime(2016, 1, 1, 23, 59, 59)); + var criteria = new TimeOfDayPersonalisationGroupCriteria(mockDateTimeProvider.Object); + var definition = string.Format(DefinitionFormat, "0900", "0930", "2300", "2359"); + + // Act + var result = criteria.MatchesVisitor(definition); + + // Assert + Assert.IsTrue(result); } } diff --git a/PersonalisationGroups.Tests/Providers/Ip/ClientIpParserTests.cs b/PersonalisationGroups.Tests/Providers/Ip/ClientIpParserTests.cs index bc9c166..27db820 100644 --- a/PersonalisationGroups.Tests/Providers/Ip/ClientIpParserTests.cs +++ b/PersonalisationGroups.Tests/Providers/Ip/ClientIpParserTests.cs @@ -1,126 +1,125 @@ using Microsoft.AspNetCore.Http; using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core.Providers.Ip; +using Our.Umbraco.PersonalisationGroups.Providers.Ip; using System.Net; -namespace Our.Umbraco.PersonalisationGroups.Tests.Providers.Ip +namespace Our.Umbraco.PersonalisationGroups.Tests.Providers.Ip; + +[TestFixture] +public class ClientIpParserTests { - [TestFixture] - public class ClientIpParserTests + private const string RemoteIp = "87.65.43.21"; + + [Test] + public void ParseClientIp_WithNoHeaders_ReturnsRemoteIpAddress() + { + // Arrange + var sut = new ClientIpParser(); + var httpContext = new DefaultHttpContext(); + httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); + + // Act + var result = sut.ParseClientIp(httpContext); + + // Assert + Assert.AreEqual(RemoteIp, result); + } + + [Test] + public void ParseClientIp_WithNoValuesInHeaders_ReturnsRemoteIpAddress() + { + // Arrange + var sut = new ClientIpParser(); + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", string.Empty); + httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); + + // Act + var result = sut.ParseClientIp(httpContext); + + // Assert + Assert.AreEqual(RemoteIp, result); + } + + [Test] + public void ParseClientIp_WithSingleIpInHeader_ReturnsIp() + { + // Arrange + var sut = new ClientIpParser(); + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", "12.34.56.78"); + httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); + + // Act + var result = sut.ParseClientIp(httpContext); + + // Assert + Assert.AreEqual("12.34.56.78", result); + } + + [Test] + public void ParseClientIp_WithSingleInvalidIpInHeader_ReturnsRemoteIp() + { + // Arrange + var sut = new ClientIpParser(); + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", "xxxx"); + httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); + + // Act + var result = sut.ParseClientIp(httpContext); + + // Assert + Assert.AreEqual(RemoteIp, result); + } + + [Test] + public void ParseClientIp_WithMultipleIpsInHeader_ReturnsIp() + { + // Arrange + var sut = new ClientIpParser(); + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", "12.34.56.78, 12.34.56.79"); + httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); + + // Act + var result = sut.ParseClientIp(httpContext); + + // Assert + Assert.AreEqual("12.34.56.78", result); + } + + [Test] + public void ParseClientIp_WithSingleIpInHeaderAndALocalIp_ReturnsIp() + { + // Arrange + var sut = new ClientIpParser(); + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", "12.34.56.78"); + httpContext.Request.Headers.Add("HTTP_X_FORWARDED_FOR", "192.168.1.1"); + httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); + + // Act + var result = sut.ParseClientIp(httpContext); + + // Assert + Assert.AreEqual("12.34.56.78", result); + } + + [Test] + public void ParseClientIp_WithSingleIpInHeaderAndALocalIpInReverseOrder_ReturnsIp() { - private const string RemoteIp = "87.65.43.21"; - - [Test] - public void ParseClientIp_WithNoHeaders_ReturnsRemoteIpAddress() - { - // Arrange - var sut = new ClientIpParser(); - var httpContext = new DefaultHttpContext(); - httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); - - // Act - var result = sut.ParseClientIp(httpContext); - - // Assert - Assert.AreEqual(RemoteIp, result); - } - - [Test] - public void ParseClientIp_WithNoValuesInHeaders_ReturnsRemoteIpAddress() - { - // Arrange - var sut = new ClientIpParser(); - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", string.Empty); - httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); - - // Act - var result = sut.ParseClientIp(httpContext); - - // Assert - Assert.AreEqual(RemoteIp, result); - } - - [Test] - public void ParseClientIp_WithSingleIpInHeader_ReturnsIp() - { - // Arrange - var sut = new ClientIpParser(); - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", "12.34.56.78"); - httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); - - // Act - var result = sut.ParseClientIp(httpContext); - - // Assert - Assert.AreEqual("12.34.56.78", result); - } - - [Test] - public void ParseClientIp_WithSingleInvalidIpInHeader_ReturnsRemoteIp() - { - // Arrange - var sut = new ClientIpParser(); - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", "xxxx"); - httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); - - // Act - var result = sut.ParseClientIp(httpContext); - - // Assert - Assert.AreEqual(RemoteIp, result); - } - - [Test] - public void ParseClientIp_WithMultipleIpsInHeader_ReturnsIp() - { - // Arrange - var sut = new ClientIpParser(); - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", "12.34.56.78, 12.34.56.79"); - httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); - - // Act - var result = sut.ParseClientIp(httpContext); - - // Assert - Assert.AreEqual("12.34.56.78", result); - } - - [Test] - public void ParseClientIp_WithSingleIpInHeaderAndALocalIp_ReturnsIp() - { - // Arrange - var sut = new ClientIpParser(); - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", "12.34.56.78"); - httpContext.Request.Headers.Add("HTTP_X_FORWARDED_FOR", "192.168.1.1"); - httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); - - // Act - var result = sut.ParseClientIp(httpContext); - - // Assert - Assert.AreEqual("12.34.56.78", result); - } - - [Test] - public void ParseClientIp_WithSingleIpInHeaderAndALocalIpInReverseOrder_ReturnsIp() - { - // Arrange - var sut = new ClientIpParser(); - var httpContext = new DefaultHttpContext(); - httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", "192.168.1.1"); - httpContext.Request.Headers.Add("HTTP_X_FORWARDED_FOR", "12.34.56.78"); - httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); - - // Act - var result = sut.ParseClientIp(httpContext); - - // Assert - Assert.AreEqual("12.34.56.78", result); - } + // Arrange + var sut = new ClientIpParser(); + var httpContext = new DefaultHttpContext(); + httpContext.Request.Headers.Add("HTTP_FORWARDED_FOR", "192.168.1.1"); + httpContext.Request.Headers.Add("HTTP_X_FORWARDED_FOR", "12.34.56.78"); + httpContext.Connection.RemoteIpAddress = IPAddress.Parse(RemoteIp); + + // Act + var result = sut.ParseClientIp(httpContext); + + // Assert + Assert.AreEqual("12.34.56.78", result); } } diff --git a/PersonalisationGroups.Tests/Providers/PagesViewed/StringExtensionsTests.cs b/PersonalisationGroups.Tests/Providers/PagesViewed/StringExtensionsTests.cs index 80dcd6c..d4dc1d7 100644 --- a/PersonalisationGroups.Tests/Providers/PagesViewed/StringExtensionsTests.cs +++ b/PersonalisationGroups.Tests/Providers/PagesViewed/StringExtensionsTests.cs @@ -1,66 +1,75 @@ using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core.Providers.PagesViewed; +using Our.Umbraco.PersonalisationGroups.Providers.PagesViewed; +using System; using System.Collections.Generic; +using System.Runtime.ConstrainedExecution; -namespace Our.Umbraco.PersonalisationGroups.Tests.Providers.PagesViewed +namespace Our.Umbraco.PersonalisationGroups.Tests.Providers.PagesViewed; + +[TestFixture] +public class StringExtensionsTests { - [TestFixture] - public class StringExtensionsTests + [Test] + public void ParsePageIds_ShouldParseEmptyValue() { - [Test] - public void ParsePageIds_ShouldParseEmptyValue() - { - // Arrange - var expected = new List(); - var cookieValue = string.Empty; + // Arrange + var expected = new List(); + var cookieValue = string.Empty; - // Act - var actual = cookieValue.ParsePageIds(); + // Act + var actual = cookieValue.ParsePageKeys(); - // Assert - CollectionAssert.AreEqual(expected, actual); - } + // Assert + CollectionAssert.AreEqual(expected, actual); + } - [Test] - public void ParsePageIds_ShouldParseValidCookie() - { - // Arrange - var expected = new List { 1, 2, 3, 4 }; - var cookieValue = "1,2,3,4"; + [Test] + public void ParsePageIds_ShouldParseValidCookie() + { + // Arrange + var expected = new List { + Guid.Parse("77d47dd7-a0e2-42a3-9103-27d5f9470ce8"), + Guid.Parse("6056e478-c614-4cb1-b389-c9fe7e950555"), + Guid.Parse("e7a3a2fc-c406-4482-88e7-c39a84f649e7") + }; + var cookieValue = "77d47dd7-a0e2-42a3-9103-27d5f9470ce8,6056e478-c614-4cb1-b389-c9fe7e950555,e7a3a2fc-c406-4482-88e7-c39a84f649e7"; - // Act - var actual = cookieValue.ParsePageIds(); + // Act + var actual = cookieValue.ParsePageKeys(); - // Assert - CollectionAssert.AreEqual(expected, actual); - } + // Assert + CollectionAssert.AreEqual(expected, actual); + } - [Test] - public void ParsePageIds_ShouldRemoveInvalidValuesFromCookie() - { - // Arrange - var expected = new List { 1, 2, 3, 4 }; - var cookieValue = "1,invalid,2,3,####,4,@!@!"; + [Test] + public void ParsePageIds_ShouldRemoveInvalidValuesFromCookie() + { + // Arrange + var expected = new List { + Guid.Parse("77d47dd7-a0e2-42a3-9103-27d5f9470ce8"), + Guid.Parse("6056e478-c614-4cb1-b389-c9fe7e950555"), + Guid.Parse("e7a3a2fc-c406-4482-88e7-c39a84f649e7") + }; + var cookieValue = "77d47dd7-a0e2-42a3-9103-27d5f9470ce8,6056e478-c614-4cb1-b389-c9fe7e950555,xxx,e7a3a2fc-c406-4482-88e7-c39a84f649e7"; - // Act - var actual = cookieValue.ParsePageIds(); + // Act + var actual = cookieValue.ParsePageKeys(); - // Assert - CollectionAssert.AreEqual(expected, actual); - } + // Assert + CollectionAssert.AreEqual(expected, actual); + } - [Test] - public void ParsePageIds_ShouldIgnoreInvalidCookie() - { - // Arrange - var expected = new List(); - var cookieValue = "ThisIsABadCookie"; + [Test] + public void ParsePageIds_ShouldIgnoreInvalidCookie() + { + // Arrange + var expected = new List(); + var cookieValue = "ThisIsABadCookie"; - // Act - var actual = cookieValue.ParsePageIds(); + // Act + var actual = cookieValue.ParsePageKeys(); - // Assert - CollectionAssert.AreEqual(expected, actual); - } + // Assert + CollectionAssert.AreEqual(expected, actual); } } diff --git a/PersonalisationGroups.Tests/Services/GroupMatchingServiceTests.cs b/PersonalisationGroups.Tests/Services/GroupMatchingServiceTests.cs index 116251d..f1708a7 100644 --- a/PersonalisationGroups.Tests/Services/GroupMatchingServiceTests.cs +++ b/PersonalisationGroups.Tests/Services/GroupMatchingServiceTests.cs @@ -1,361 +1,378 @@ using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core.Configuration; -using Our.Umbraco.PersonalisationGroups.Core.Criteria; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.DayOfWeek; -using Our.Umbraco.PersonalisationGroups.Core.Criteria.TimeOfDay; -using Our.Umbraco.PersonalisationGroups.Core.Providers.DateTime; -using Our.Umbraco.PersonalisationGroups.Core.Services; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Criteria; +using Our.Umbraco.PersonalisationGroups.Criteria.DayOfWeek; +using Our.Umbraco.PersonalisationGroups.Criteria.TimeOfDay; +using Our.Umbraco.PersonalisationGroups.Providers.DateTime; +using Our.Umbraco.PersonalisationGroups.Services; using Our.Umbraco.PersonalisationGroups.Tests.TestHelpers; using System.Collections.Generic; using Umbraco.Cms.Core.Models.PublishedContent; -namespace Our.Umbraco.PersonalisationGroups.Tests.Services +namespace Our.Umbraco.PersonalisationGroups.Tests.Services; + +[TestFixture] +public class GroupMatchingServiceTests { - [TestFixture] - public class GroupMatchingServiceTests + [Test] + public void GroupMatchingService_MatchSingleGroup_WithDisabledPackage_ReturnsTrue() { - [Test] - public void GroupMatchingService_MatchSingleGroup_WithDisabledPackage_ReturnsTrue() - { - // Arrange - var sut = CreateGroupMatchingService(withDisabledPackageConfig: true); - var groupContent = CreateContent(); + // Arrange + var sut = CreateGroupMatchingService(withDisabledPackageConfig: true); + var groupContent = CreateContent(); - // Act - var result = sut.MatchGroup(groupContent); + // Act + var result = sut.MatchGroup(groupContent); - // Assert - Assert.IsTrue(result); - } + // Assert + Assert.IsTrue(result); + } - [Test] - public void GroupMatchingService_MatchSingleGroup_WithSingleDefinitionDetail_Matches_ReturnsTrue() + [Test] + public void GroupMatchingService_MatchSingleGroup_WithSingleDefinitionDetail_Matches_ReturnsTrue() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.All, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.All, - Details = new List - { - Definitions.MatchingDayOfWeekDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.MatchGroup(groupContent); - - // Assert - Assert.IsTrue(result); - } + Definitions.MatchingDayOfWeekDefinition(), + } + }; + var groupContent = CreateContent(definition); + + // Act + var result = sut.MatchGroup(groupContent); + + // Assert + Assert.IsTrue(result); + } - [Test] - public void GroupMatchingService_MatchSingleGroup_WithSingleDefinitionDetail_DoesNotMatch_ReturnsFalse() + [Test] + public void GroupMatchingService_MatchSingleGroup_WithSingleDefinitionDetail_DoesNotMatch_ReturnsFalse() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.All, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.All, - Details = new List - { - Definitions.NonMatchingDayOfWeekDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.MatchGroup(groupContent); - - // Assert - Assert.IsFalse(result); - } + Definitions.NonMatchingDayOfWeekDefinition(), + } + }; + var groupContent = CreateContent(definition); + + // Act + var result = sut.MatchGroup(groupContent); + + // Assert + Assert.IsFalse(result); + } - [Test] - public void GroupMatchingService_MatchSingleGroup_WithMultipleDefinitionDetails_ForAll_MatchingAll_ReturnsTrue() + [Test] + public void GroupMatchingService_MatchSingleGroup_WithMultipleDefinitionDetails_ForAll_MatchingAll_ReturnsTrue() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.All, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.All, - Details = new List - { - Definitions.MatchingDayOfWeekDefinition(), - Definitions.MatchingTimeOfDayDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.MatchGroup(groupContent); - - // Assert - Assert.IsTrue(result); - } + Definitions.MatchingDayOfWeekDefinition(), + Definitions.MatchingTimeOfDayDefinition(), + } + }; + var groupContent = CreateContent(definition); + + // Act + var result = sut.MatchGroup(groupContent); - [Test] - public void GroupMatchingService_MatchSingleGroup_WithMultipleDefinitionDetails_ForAll_NotMatchingAll_ReturnsFalse() + // Assert + Assert.IsTrue(result); + } + + [Test] + public void GroupMatchingService_MatchSingleGroup_WithMultipleDefinitionDetails_ForAll_NotMatchingAll_ReturnsFalse() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.All, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.All, - Details = new List - { - Definitions.NonMatchingDayOfWeekDefinition(), - Definitions.MatchingTimeOfDayDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.MatchGroup(groupContent); - - // Assert - Assert.IsFalse(result); - } + Definitions.NonMatchingDayOfWeekDefinition(), + Definitions.MatchingTimeOfDayDefinition(), + } + }; + var groupContent = CreateContent(definition); + + // Act + var result = sut.MatchGroup(groupContent); - [Test] - public void GroupMatchingService_MatchSingleGroup_WithMultiplDefinitionDetails_ForAny_MatchingAll_ReturnsTrue() + // Assert + Assert.IsFalse(result); + } + + [Test] + public void GroupMatchingService_MatchSingleGroup_WithMultiplDefinitionDetails_ForAny_MatchingAll_ReturnsTrue() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.Any, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.Any, - Details = new List - { - Definitions.MatchingDayOfWeekDefinition(), - Definitions.MatchingTimeOfDayDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.MatchGroup(groupContent); - - // Assert - Assert.IsTrue(result); - } + Definitions.MatchingDayOfWeekDefinition(), + Definitions.MatchingTimeOfDayDefinition(), + } + }; + var groupContent = CreateContent(definition); + + // Act + var result = sut.MatchGroup(groupContent); - [Test] - public void GroupMatchingService_MatchSingleGroup_WithMultipleDefinitionDetails_ForAny_MatchingOne_ReturnsTrue() + // Assert + Assert.IsTrue(result); + } + + [Test] + public void GroupMatchingService_MatchSingleGroup_WithMultipleDefinitionDetails_ForAny_MatchingOne_ReturnsTrue() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.Any, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.Any, - Details = new List - { - Definitions.NonMatchingDayOfWeekDefinition(), - Definitions.MatchingTimeOfDayDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.MatchGroup(groupContent); - - // Assert - Assert.IsTrue(result); - } + Definitions.NonMatchingDayOfWeekDefinition(), + Definitions.MatchingTimeOfDayDefinition(), + } + }; + var groupContent = CreateContent(definition); - [Test] - public void GroupMatchingService_ScoreSingleGroup_WithDisabledPackage_ReturnsZero() - { - // Arrange - var sut = CreateGroupMatchingService(withDisabledPackageConfig: true); - var groupContent = CreateContent(); + // Act + var result = sut.MatchGroup(groupContent); - // Act - var result = sut.ScoreGroup(groupContent); + // Assert + Assert.IsTrue(result); + } - // Assert - Assert.AreEqual(0, result); - } + [Test] + public void GroupMatchingService_ScoreSingleGroup_WithDisabledPackage_ReturnsZero() + { + // Arrange + var sut = CreateGroupMatchingService(withDisabledPackageConfig: true); + var groupContent = CreateContent(); - [Test] - public void GroupMatchingService_ScoreSingleGroup_WithSingleDefinitionDetail_Match_ReturnCorrectScore() - { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition - { - Match = PersonalisationGroupDefinitionMatch.All, - Score = 50, - Details = new List - { - Definitions.MatchingDayOfWeekDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.ScoreGroup(groupContent); - - // Assert - Assert.AreEqual(50, result); - } + // Act + var result = sut.ScoreGroup(groupContent); + + // Assert + Assert.AreEqual(0, result); + } - [Test] - public void GroupMatchingService_ScoreSingleGroup_WithSingleDefinitionDetail_DoesNotMatch_ReturnCorrectScore() + [Test] + public void GroupMatchingService_ScoreSingleGroup_WithSingleDefinitionDetail_Match_ReturnCorrectScore() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.All, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.All, - Score = 50, - Details = new List - { - Definitions.NonMatchingDayOfWeekDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.ScoreGroup(groupContent); - - // Assert - Assert.AreEqual(0, result); - } + Definitions.MatchingDayOfWeekDefinition(), + } + }; + var groupContent = CreateContent(definition); + + // Act + var result = sut.ScoreGroup(groupContent); + + // Assert + Assert.AreEqual(50, result); + } - [Test] - public void GroupMatchingService_ScoreSingleGroup_WithMultipleDefinitionDetails_ForAll_MatchingAll_ReturnCorrectScore() + [Test] + public void GroupMatchingService_ScoreSingleGroup_WithSingleDefinitionDetail_DoesNotMatch_ReturnCorrectScore() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.All, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.All, - Score = 50, - Details = new List - { - Definitions.MatchingDayOfWeekDefinition(), - Definitions.MatchingTimeOfDayDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.ScoreGroup(groupContent); - - // Assert - Assert.AreEqual(50, result); - } + Definitions.NonMatchingDayOfWeekDefinition(), + } + }; + var groupContent = CreateContent(definition); + + // Act + var result = sut.ScoreGroup(groupContent); + + // Assert + Assert.AreEqual(0, result); + } - [Test] - public void GroupMatchingService_ScoreSingleGroup_WithMultipleDefinitionDetails_ForAll_NotMatchingAll_ReturnCorrectScore() + [Test] + public void GroupMatchingService_ScoreSingleGroup_WithMultipleDefinitionDetails_ForAll_MatchingAll_ReturnCorrectScore() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.All, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.All, - Score = 50, - Details = new List - { - Definitions.NonMatchingDayOfWeekDefinition(), - Definitions.MatchingTimeOfDayDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.ScoreGroup(groupContent); - - // Assert - Assert.AreEqual(0, result); - } + Definitions.MatchingDayOfWeekDefinition(), + Definitions.MatchingTimeOfDayDefinition(), + } + }; + var groupContent = CreateContent(definition); + + // Act + var result = sut.ScoreGroup(groupContent); + + // Assert + Assert.AreEqual(50, result); + } - [Test] - public void GroupMatchingService_ScoreSingleGroup_WithMultipleScore_ForAny_MatchingAll_ReturnCorrectScore() + [Test] + public void GroupMatchingService_ScoreSingleGroup_WithMultipleDefinitionDetails_ForAll_NotMatchingAll_ReturnCorrectScore() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.All, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.Any, - Score = 50, - Details = new List - { - Definitions.MatchingDayOfWeekDefinition(), - Definitions.MatchingTimeOfDayDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.ScoreGroup(groupContent); - - // Assert - Assert.AreEqual(50, result); - } + Definitions.NonMatchingDayOfWeekDefinition(), + Definitions.MatchingTimeOfDayDefinition(), + } + }; + var groupContent = CreateContent(definition); - [Test] - public void GroupMatchingService_ScoreSingleGroup_WithMultipleScore_ForAny_MatchingOne_ReturnCorrectScore() + // Act + var result = sut.ScoreGroup(groupContent); + + // Assert + Assert.AreEqual(0, result); + } + + [Test] + public void GroupMatchingService_ScoreSingleGroup_WithMultipleScore_ForAny_MatchingAll_ReturnCorrectScore() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - // Arrange - var sut = CreateGroupMatchingService(); - var definition = new PersonalisationGroupDefinition + Match = PersonalisationGroupDefinitionMatch.Any, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - Match = PersonalisationGroupDefinitionMatch.Any, - Score = 50, - Details = new List - { - Definitions.NonMatchingDayOfWeekDefinition(), - Definitions.MatchingTimeOfDayDefinition(), - } - }; - var groupContent = CreateContent(definition); - - // Act - var result = sut.ScoreGroup(groupContent); - - // Assert - Assert.AreEqual(50, result); - } + Definitions.MatchingDayOfWeekDefinition(), + Definitions.MatchingTimeOfDayDefinition(), + } + }; + var groupContent = CreateContent(definition); + + // Act + var result = sut.ScoreGroup(groupContent); - private IGroupMatchingService CreateGroupMatchingService(bool withDisabledPackageConfig = false) + // Assert + Assert.AreEqual(50, result); + } + + [Test] + public void GroupMatchingService_ScoreSingleGroup_WithMultipleScore_ForAny_MatchingOne_ReturnCorrectScore() + { + // Arrange + var sut = CreateGroupMatchingService(); + var definition = new PersonalisationGroupDefinition { - var config = Options.Create(new PersonalisationGroupsConfig + Match = PersonalisationGroupDefinitionMatch.Any, + Duration = PersonalisationGroupDefinitionDuration.Page, + Score = 50, + Details = new List { - DisablePackage = withDisabledPackageConfig - }); + Definitions.NonMatchingDayOfWeekDefinition(), + Definitions.MatchingTimeOfDayDefinition(), + } + }; + var groupContent = CreateContent(definition); - var dateTimeProvider = new DateTimeProvider(); - var mockCriteriaService = new Mock(); - mockCriteriaService.Setup(x => x.GetAvailableCriteria()).Returns(new List - { - new DayOfWeekPersonalisationGroupCriteria(dateTimeProvider), - new TimeOfDayPersonalisationGroupCriteria(dateTimeProvider), - }); - - var mockStickyMatchService = new Mock(); - - return new GroupMatchingService(config, mockCriteriaService.Object, mockStickyMatchService.Object, new NoopPublishedValueFallback()); - } + // Act + var result = sut.ScoreGroup(groupContent); - private static IPublishedContent CreateContent(PersonalisationGroupDefinition definition = null) + // Assert + Assert.AreEqual(50, result); + } + + private IGroupMatchingService CreateGroupMatchingService(bool withDisabledPackageConfig = false) + { + var config = Options.Create(new PersonalisationGroupsConfig { - var mockContent = new Mock(); + DisablePackage = withDisabledPackageConfig + }); - if (definition != null) - { - var property = new Mock(); - property.Setup(x => x.Alias).Returns(Core.AppConstants.PersonalisationGroupDefinitionPropertyAlias); - property.Setup(x => x.GetValue(It.IsAny(), It.IsAny())).Returns(definition); - property.Setup(x => x.HasValue(It.IsAny(), It.IsAny())).Returns(true); - mockContent.Setup(x => x.GetProperty(Core.AppConstants.PersonalisationGroupDefinitionPropertyAlias)).Returns(property.Object); - } + var dateTimeProvider = new DateTimeProvider(); + var mockCriteriaService = new Mock(); + mockCriteriaService.Setup(x => x.GetAvailableCriteria()).Returns(new List + { + new DayOfWeekPersonalisationGroupCriteria(dateTimeProvider), + new TimeOfDayPersonalisationGroupCriteria(dateTimeProvider), + }); + + var mockStickyMatchService = new Mock(); + + return new GroupMatchingService(config, mockCriteriaService.Object, mockStickyMatchService.Object, new NoopPublishedValueFallback()); + } + + private static IPublishedContent CreateContent(PersonalisationGroupDefinition? definition = null) + { + var mockContent = new Mock(); - return mockContent.Object; + if (definition != null) + { + var property = new Mock(); + property.Setup(x => x.Alias).Returns(AppConstants.PersonalisationGroupDefinitionPropertyAlias); + property.Setup(x => x.GetValue(It.IsAny(), It.IsAny())).Returns(definition); + property.Setup(x => x.HasValue(It.IsAny(), It.IsAny())).Returns(true); + mockContent.Setup(x => x.GetProperty(AppConstants.PersonalisationGroupDefinitionPropertyAlias)).Returns(property.Object); } + + return mockContent.Object; } } diff --git a/PersonalisationGroups.Tests/Services/UserActivityTrackerTests.cs b/PersonalisationGroups.Tests/Services/UserActivityTrackerTests.cs index 9c40076..36d438c 100644 --- a/PersonalisationGroups.Tests/Services/UserActivityTrackerTests.cs +++ b/PersonalisationGroups.Tests/Services/UserActivityTrackerTests.cs @@ -1,69 +1,69 @@ using NUnit.Framework; -using Our.Umbraco.PersonalisationGroups.Core.Services; +using Our.Umbraco.PersonalisationGroups.Services; +using System; -namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.PagesViewed +namespace Our.Umbraco.PersonalisationGroups.Tests.Criteria.PagesViewed; + +[TestFixture] +public class UserActivityTrackerTests { - [TestFixture] - public class UserActivityTrackerTests + [Test] + public void AppendPageIdIfNotPreviouslyViewed_ShouldAddPageKeyToEmptyCookie() { - [Test] - public void AppendPageIdIfNotPreviouslyViewed_ShouldAddPageIdToEmptyCookie() - { - // Arrange - var expected = "1000"; - var cookieValue = string.Empty; - var pageId = 1000; + // Arrange + var expected = "77d47dd7-a0e2-42a3-9103-27d5f9470ce8"; + var cookieValue = string.Empty; + var pageKey = Guid.Parse("77d47dd7-a0e2-42a3-9103-27d5f9470ce8"); - // Act - var actual = UserActivityTracker.AppendPageIdIfNotPreviouslyViewed(cookieValue, pageId); + // Act + var actual = UserActivityTracker.AppendPageIdIfNotPreviouslyViewed(cookieValue, pageKey); - // Assert - Assert.AreEqual(expected, actual); - } + // Assert + Assert.AreEqual(expected, actual); + } - [Test] - public void AppendPageIdIfNotPreviouslyViewed_ShouldAddPageIdToValidCookie() - { - // Arrange - var expected = "1,2,3,4,1000"; - var cookieValue = "1,2,3,4"; - var pageId = 1000; + [Test] + public void AppendPageIdIfNotPreviouslyViewed_ShouldAddPageIdToValidCookie() + { + // Arrange + var expected = "77d47dd7-a0e2-42a3-9103-27d5f9470ce8,6056e478-c614-4cb1-b389-c9fe7e950555"; + var cookieValue = "77d47dd7-a0e2-42a3-9103-27d5f9470ce8"; + var pageKey = Guid.Parse("6056e478-c614-4cb1-b389-c9fe7e950555"); - // Act - var actual = UserActivityTracker.AppendPageIdIfNotPreviouslyViewed(cookieValue, pageId); + // Act + var actual = UserActivityTracker.AppendPageIdIfNotPreviouslyViewed(cookieValue, pageKey); - // Assert - Assert.AreEqual(expected, actual); - } + // Assert + Assert.AreEqual(expected, actual); + } - [Test] - public void AppendPageIdIfNotPreviouslyViewed_ShouldRemoveInvalidValuesAndAddPageIdToCookie() - { - // Arrange - var expected = "1,2,3,4,1000"; - var cookieValue = "1,invalid,2,3,####,4,@!@!"; - var pageId = 1000; + [Test] + public void AppendPageIdIfNotPreviouslyViewed_ShouldRemoveInvalidValuesAndAddPageIdToCookie() + { + // Arrange + var expected = "77d47dd7-a0e2-42a3-9103-27d5f9470ce8,6056e478-c614-4cb1-b389-c9fe7e950555"; + var cookieValue = "77d47dd7-a0e2-42a3-9103-27d5f9470ce8,xxx"; + var pageKey = Guid.Parse("6056e478-c614-4cb1-b389-c9fe7e950555"); - // Act - var actual = UserActivityTracker.AppendPageIdIfNotPreviouslyViewed(cookieValue, pageId); + // Act + var actual = UserActivityTracker.AppendPageIdIfNotPreviouslyViewed(cookieValue, pageKey); - // Assert - Assert.AreEqual(expected, actual); - } + // Assert + Assert.AreEqual(expected, actual); + } - [Test] - public void AppendPageIdIfNotPreviouslyViewed_ShouldIgnoreInvalidCookieAndAddPageId() - { - // Arrange - var expected = "1000"; - var cookieValue = "ThisIsABadCookie"; - var pageId = 1000; + [Test] + public void AppendPageIdIfNotPreviouslyViewed_ShouldIgnoreInvalidCookieAndAddPageId() + { + // Arrange + var expected = "77d47dd7-a0e2-42a3-9103-27d5f9470ce8"; + var cookieValue = "ThisIsABadCookie"; + var pageKey = Guid.Parse("77d47dd7-a0e2-42a3-9103-27d5f9470ce8"); - // Act - var actual = UserActivityTracker.AppendPageIdIfNotPreviouslyViewed(cookieValue, pageId); + // Act + var actual = UserActivityTracker.AppendPageIdIfNotPreviouslyViewed(cookieValue, pageKey); - // Assert - Assert.AreEqual(expected, actual); - } + // Assert + Assert.AreEqual(expected, actual); } } diff --git a/PersonalisationGroups.Tests/TestHelpers/Definitions.cs b/PersonalisationGroups.Tests/TestHelpers/Definitions.cs index 06c00af..4a63f09 100644 --- a/PersonalisationGroups.Tests/TestHelpers/Definitions.cs +++ b/PersonalisationGroups.Tests/TestHelpers/Definitions.cs @@ -1,26 +1,25 @@ -using Our.Umbraco.PersonalisationGroups.Core.Criteria; +using Our.Umbraco.PersonalisationGroups.Criteria; using System; -namespace Our.Umbraco.PersonalisationGroups.Tests.TestHelpers +namespace Our.Umbraco.PersonalisationGroups.Tests.TestHelpers; + +public static class Definitions { - public static class Definitions + public static PersonalisationGroupDefinitionDetail MatchingDayOfWeekDefinition() => new PersonalisationGroupDefinitionDetail { - public static PersonalisationGroupDefinitionDetail MatchingDayOfWeekDefinition() => new PersonalisationGroupDefinitionDetail - { - Alias = "dayOfWeek", - Definition = $"[ {(int)DateTime.Now.DayOfWeek + 1} ]", - }; + Alias = "dayOfWeek", + Definition = $"[ {(int)DateTime.Now.DayOfWeek + 1} ]", + }; - public static PersonalisationGroupDefinitionDetail NonMatchingDayOfWeekDefinition() => new PersonalisationGroupDefinitionDetail - { - Alias = "dayOfWeek", - Definition = $"[ {(int)DateTime.Now.DayOfWeek + 2} ]", - }; + public static PersonalisationGroupDefinitionDetail NonMatchingDayOfWeekDefinition() => new PersonalisationGroupDefinitionDetail + { + Alias = "dayOfWeek", + Definition = $"[ {(int)DateTime.Now.DayOfWeek + 2} ]", + }; - public static PersonalisationGroupDefinitionDetail MatchingTimeOfDayDefinition() => new PersonalisationGroupDefinitionDetail - { - Alias = "timeOfDay", - Definition = "[ { \"from\": \"0000\", \"to\": \"2359\" } ]" - }; - } + public static PersonalisationGroupDefinitionDetail MatchingTimeOfDayDefinition() => new PersonalisationGroupDefinitionDetail + { + Alias = "timeOfDay", + Definition = "[ { \"from\": \"0000\", \"to\": \"2359\" } ]" + }; } diff --git a/PersonalisationGroups.Tests/UmbracoPersonalisationGroups.Tests.csproj b/PersonalisationGroups.Tests/UmbracoPersonalisationGroups.Tests.csproj index b8bbd77..82a4085 100644 --- a/PersonalisationGroups.Tests/UmbracoPersonalisationGroups.Tests.csproj +++ b/PersonalisationGroups.Tests/UmbracoPersonalisationGroups.Tests.csproj @@ -3,14 +3,12 @@ Our.Umbraco.PersonalisationGroups.Tests Our.Umbraco.PersonalisationGroups.Tests - net5.0 + net8.0 false + enable + nullable - - - - @@ -18,7 +16,11 @@ - + + + + + diff --git a/PersonalisationGroups/Api/Configuration/BackOfficeSecurityRequirementsOperationFilter.cs b/PersonalisationGroups/Api/Configuration/BackOfficeSecurityRequirementsOperationFilter.cs new file mode 100644 index 0000000..5296515 --- /dev/null +++ b/PersonalisationGroups/Api/Configuration/BackOfficeSecurityRequirementsOperationFilter.cs @@ -0,0 +1,8 @@ +using Umbraco.Cms.Api.Management.OpenApi; + +namespace Our.Umbraco.PersonalisationGroups.Api.Configuration; + +public class BackOfficeSecurityRequirementsOperationFilter : BackOfficeSecurityRequirementsOperationFilterBase +{ + protected override string ApiName => AppConstants.ManagementApi.ApiName; +} diff --git a/PersonalisationGroups/Api/Management/Continent/GetCollectionController.cs b/PersonalisationGroups/Api/Management/Continent/GetCollectionController.cs new file mode 100644 index 0000000..72f822c --- /dev/null +++ b/PersonalisationGroups/Api/Management/Continent/GetCollectionController.cs @@ -0,0 +1,66 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Api.Management.MemberType; +using Our.Umbraco.PersonalisationGroups.Api.Models; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Extensions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Umbraco.Cms.Core.Cache; + +namespace Our.Umbraco.PersonalisationGroups.Api.Management.Continent; + +[ApiVersion("1.0")] +[ApiExplorerSettings(GroupName = "Geo Location")] +public class GetCollectionController : PersonalisationGroupsGeoLocationControllerBase +{ + public GetCollectionController( + IOptions config, + IHostEnvironment hostEnvironment, + AppCaches appCaches) + : base(config, hostEnvironment, appCaches) + { + } + + [HttpGet("continent/")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public IActionResult GetContinentCollection() + { + var cacheKey = $"PersonalisationGroups_GeoLocation_Continents"; + var continents = AppCaches.RuntimeCache.Get(cacheKey, + () => + { + var assembly = GetResourceAssembly(); + var resourceName = GetResourceName("continents"); + using (var stream = assembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) + { + return null; + } + + using (var reader = new StreamReader(stream)) + { + var continentRecords = reader.ReadToEnd() + .SplitByNewLine(StringSplitOptions.RemoveEmptyEntries) + .Select(x => new ContinentDto + { + Code = x.Split(',')[0], + Name = CleanName(x.Split(',')[1]) + }); + + continentRecords = continentRecords.OrderBy(x => x.Name); + + return continentRecords; + } + } + }); + + return Ok(continents); + } +} diff --git a/PersonalisationGroups/Api/Management/Country/GetCollectionController.cs b/PersonalisationGroups/Api/Management/Country/GetCollectionController.cs new file mode 100644 index 0000000..538358e --- /dev/null +++ b/PersonalisationGroups/Api/Management/Country/GetCollectionController.cs @@ -0,0 +1,95 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Api.Management.MemberType; +using Our.Umbraco.PersonalisationGroups.Api.Models; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Extensions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Umbraco.Cms.Core.Cache; + +namespace Our.Umbraco.PersonalisationGroups.Api.Management.Country; + +[ApiVersion("1.0")] +[ApiExplorerSettings(GroupName = "Geo Location")] +public class GetCollectionController : PersonalisationGroupsGeoLocationControllerBase +{ + public GetCollectionController( + IOptions config, + IHostEnvironment hostEnvironment, + AppCaches appCaches) + : base(config, hostEnvironment, appCaches) + { + } + + [HttpGet("country/")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public IActionResult GetCountryCollection(bool withRegionsOnly = false) + { + var cacheKey = $"PersonalisationGroups_GeoLocation_Countries_{withRegionsOnly}"; + var countries = AppCaches.RuntimeCache.Get(cacheKey, + () => + { + var assembly = GetResourceAssembly(); + var resourceName = GetResourceName("countries"); + using (var stream = assembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) + { + return null; + } + + using (var reader = new StreamReader(stream)) + { + var countryRecords = reader.ReadToEnd() + .SplitByNewLine(StringSplitOptions.RemoveEmptyEntries) + .Select(x => new CountryDto + { + Code = x.Split(',')[0], + Name = CleanName(x.Split(',')[1]) + }); + + if (withRegionsOnly) + { + var countryCodesWithRegions = GetCountryCodesWithRegions(assembly); + countryRecords = countryRecords + .Where(x => countryCodesWithRegions.Contains(x.Code)); + } + + countryRecords = countryRecords.OrderBy(x => x.Name); + + return countryRecords; + } + } + }); + + return Ok(countries); + } + + private static IEnumerable GetCountryCodesWithRegions(Assembly assembly) + { + var resourceName = GetResourceName("regions"); + using (var stream = assembly.GetManifestResourceStream(resourceName)) + { + if (stream == null) + { + return new string[0]; + } + + using (var reader = new StreamReader(stream)) + { + return reader.ReadToEnd() + .Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Split(',')[0]) + .Distinct() + .ToArray(); + } + } + } +} diff --git a/PersonalisationGroups/Api/Management/Criteria/GetCollectionController.cs b/PersonalisationGroups/Api/Management/Criteria/GetCollectionController.cs new file mode 100644 index 0000000..5c1f003 --- /dev/null +++ b/PersonalisationGroups/Api/Management/Criteria/GetCollectionController.cs @@ -0,0 +1,33 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Our.Umbraco.PersonalisationGroups.Api.Models; +using Our.Umbraco.PersonalisationGroups.Services; +using System.Collections.Generic; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Api.Management.Criteria; + +[ApiVersion("1.0")] +[ApiExplorerSettings(GroupName = "Criteria")] +public class GetCollectionController : PersonalisationGroupsControllerBase +{ + private readonly ICriteriaService _criteriaService; + + public GetCollectionController(ICriteriaService criteriaService) => _criteriaService = criteriaService; + + [HttpGet("criteria/")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public IActionResult GetCriteriaCollection() + { + var dtos = _criteriaService.GetAvailableCriteria() + .Select(x => new CriteriaDto + { + Alias = x.Alias, + Description = x.Description, + Name = x.Name + }) + .ToList(); + return Ok(dtos); + } +} diff --git a/PersonalisationGroups/Api/Management/MemberGroup/GetCollectionController.cs b/PersonalisationGroups/Api/Management/MemberGroup/GetCollectionController.cs new file mode 100644 index 0000000..69fbff9 --- /dev/null +++ b/PersonalisationGroups/Api/Management/MemberGroup/GetCollectionController.cs @@ -0,0 +1,33 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Our.Umbraco.PersonalisationGroups.Api.Models; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Umbraco.Cms.Core.Services; + +namespace Our.Umbraco.PersonalisationGroups.Api.Management.MemberGroup; + +[ApiVersion("1.0")] +[ApiExplorerSettings(GroupName = "Member")] +public class GetCollectionController : PersonalisationGroupsControllerBase +{ + private readonly IMemberGroupService _memberGroupService; + + public GetCollectionController(IMemberGroupService memberGroupService) => _memberGroupService = memberGroupService; + + [HttpGet("member-group/")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public async Task GetMemberGroupCollection() + { + var dtos = (await _memberGroupService.GetAllAsync()) + .Select(x => new MemberGroupDto + { + Name = x.Name ?? string.Empty + }) + .OrderBy(x => x.Name) + .ToList(); + return Ok(dtos); + } +} diff --git a/PersonalisationGroups/Api/Management/MemberProfileField/GetCollectionController.cs b/PersonalisationGroups/Api/Management/MemberProfileField/GetCollectionController.cs new file mode 100644 index 0000000..2053856 --- /dev/null +++ b/PersonalisationGroups/Api/Management/MemberProfileField/GetCollectionController.cs @@ -0,0 +1,34 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Our.Umbraco.PersonalisationGroups.Api.Models; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Services; + +namespace Our.Umbraco.PersonalisationGroups.Api.Management.MemberProfileField; + +[ApiVersion("1.0")] +[ApiExplorerSettings(GroupName = "Member")] +public class GetCollectionController : PersonalisationGroupsControllerBase +{ + private readonly IMemberTypeService _memberTypeService; + + public GetCollectionController(IMemberTypeService memberTypeService) => _memberTypeService = memberTypeService; + + [HttpGet("member-profile-field/")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public IActionResult GetMemberProfileFieldCollection() + { + var dtos = _memberTypeService.GetAll() + .SelectMany(x => x.PropertyTypes) + .Select(x => new MemberProfileFieldDto + { + Alias = x.Alias, + Name = x.Name ?? string.Empty + }) + .OrderBy(x => x.Name) + .ToList(); + return Ok(dtos); + } +} diff --git a/PersonalisationGroups/Api/Management/MemberType/GetCollectionController.cs b/PersonalisationGroups/Api/Management/MemberType/GetCollectionController.cs new file mode 100644 index 0000000..bb948f2 --- /dev/null +++ b/PersonalisationGroups/Api/Management/MemberType/GetCollectionController.cs @@ -0,0 +1,33 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Our.Umbraco.PersonalisationGroups.Api.Models; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Services; + +namespace Our.Umbraco.PersonalisationGroups.Api.Management.MemberType; + +[ApiVersion("1.0")] +[ApiExplorerSettings(GroupName = "Member")] +public class GetCollectionController : PersonalisationGroupsControllerBase +{ + private readonly IMemberTypeService _memberTypeService; + + public GetCollectionController(IMemberTypeService memberTypeService) => _memberTypeService = memberTypeService; + + [HttpGet("member-type/")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public IActionResult GetMemberTypeCollection() + { + var dtos = _memberTypeService.GetAll() + .Select(x => new MemberTypeDto + { + Alias = x.Alias, + Name = x.Name ?? string.Empty + }) + .OrderBy(x => x.Name) + .ToList(); + return Ok(dtos); + } +} diff --git a/PersonalisationGroups/Api/Management/PersonalisationGroupsControllerBase.cs b/PersonalisationGroups/Api/Management/PersonalisationGroupsControllerBase.cs new file mode 100644 index 0000000..d7218c8 --- /dev/null +++ b/PersonalisationGroups/Api/Management/PersonalisationGroupsControllerBase.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Umbraco.Cms.Api.Common.Attributes; +using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Cms.Web.Common.Routing; + +namespace Our.Umbraco.PersonalisationGroups.Api.Management; + +[ApiController] +[BackOfficeRoute($"{AppConstants.ManagementApi.RootPath}/v{{version:apiVersion}}")] +[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] +[MapToApi(AppConstants.ManagementApi.ApiName)] +public abstract class PersonalisationGroupsControllerBase : Controller +{ +} diff --git a/PersonalisationGroups/Api/Management/PersonalisationGroupsGeoLocationControllerBase.cs b/PersonalisationGroups/Api/Management/PersonalisationGroupsGeoLocationControllerBase.cs new file mode 100644 index 0000000..a35365c --- /dev/null +++ b/PersonalisationGroups/Api/Management/PersonalisationGroupsGeoLocationControllerBase.cs @@ -0,0 +1,32 @@ +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; +using System.Reflection; +using Umbraco.Cms.Core.Cache; + +namespace Our.Umbraco.PersonalisationGroups.Api.Management.MemberType; + +public abstract class PersonalisationGroupsGeoLocationControllerBase : PersonalisationGroupsControllerBase +{ + protected PersonalisationGroupsGeoLocationControllerBase( + IOptions config, + IHostEnvironment hostEnvironment, + AppCaches appCaches) + { + Config = config; + HostEnvironment = hostEnvironment; + AppCaches = appCaches; + } + + protected IOptions Config { get; } + + protected IHostEnvironment HostEnvironment { get; } + + protected AppCaches AppCaches { get; } + + protected static Assembly GetResourceAssembly() => Assembly.Load(AppConstants.CommonAssemblyName); + + protected static string GetResourceName(string area) => $"{AppConstants.CommonAssemblyName}.Data.{area}.txt"; + + protected static string CleanName(string name) => name.Replace("\"", string.Empty).Trim(); +} diff --git a/PersonalisationGroups/Api/Management/Region/GetCollectionController.cs b/PersonalisationGroups/Api/Management/Region/GetCollectionController.cs new file mode 100644 index 0000000..322f8a7 --- /dev/null +++ b/PersonalisationGroups/Api/Management/Region/GetCollectionController.cs @@ -0,0 +1,85 @@ +using Asp.Versioning; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Api.Management.MemberType; +using Our.Umbraco.PersonalisationGroups.Api.Models; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Extensions; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Extensions; + +namespace Our.Umbraco.PersonalisationGroups.Api.Management.Region; + +[ApiVersion("1.0")] +[ApiExplorerSettings(GroupName = "Geo Location")] +public class GetCollectionController : PersonalisationGroupsGeoLocationControllerBase +{ + public GetCollectionController( + IOptions config, + IHostEnvironment hostEnvironment, + AppCaches appCaches) + : base(config, hostEnvironment, appCaches) + { + } + + [HttpGet("country/{countryCode}/region/")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public IActionResult GetRegionCollection(string countryCode) + { + var cacheKey = $"PersonalisationGroups_GeoLocation_Regions_{countryCode}"; + var regions = AppCaches.RuntimeCache.Get(cacheKey, + () => + { + using (var stream = GetStreamForRegions()) + { + if (stream == null) + { + return null; + } + + using (var reader = new StreamReader(stream)) + { + var streamContents = reader.ReadToEnd(); + var regionRecords = streamContents + .SplitByNewLine(StringSplitOptions.RemoveEmptyEntries) + .Where(x => x.Split(',')[0] == countryCode.ToUpperInvariant()) + .Select(x => new RegionDto + { + Code = x.Split(',')[1], + Name = CleanName(x.Split(',')[2]) + }) + .OrderBy(x => x.Name); + return regionRecords; + } + } + }); + + return Ok(regions); + } + + private Stream GetStreamForRegions() + { + // First try to use custom file path if provided in configuration. + var customFilePath = Config.Value.GeoLocationRegionListPath; + if (!string.IsNullOrEmpty(customFilePath)) + { + var mappedPath = HostEnvironment.MapPathContentRoot(customFilePath); + if (!string.IsNullOrEmpty(mappedPath) && System.IO.File.Exists(mappedPath)) + { + return System.IO.File.OpenRead(mappedPath); + } + } + + // Otherwise fall back to provided resource file. + var assembly = GetResourceAssembly(); + var resourceName = GetResourceName("regions"); + return assembly.GetManifestResourceStream(resourceName) + ?? throw new ArgumentException($"Could not retrieve stream from resource: {resourceName}"); + } +} diff --git a/PersonalisationGroups/Api/Models/ContinentDto.cs b/PersonalisationGroups/Api/Models/ContinentDto.cs new file mode 100644 index 0000000..b853c1c --- /dev/null +++ b/PersonalisationGroups/Api/Models/ContinentDto.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Api.Models; + +public class ContinentDto +{ + public required string Code { get; init; } + + public required string Name { get; init; } +} diff --git a/PersonalisationGroups/Api/Models/CountryDto.cs b/PersonalisationGroups/Api/Models/CountryDto.cs new file mode 100644 index 0000000..5e8ec2e --- /dev/null +++ b/PersonalisationGroups/Api/Models/CountryDto.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Api.Models; + +public class CountryDto +{ + public required string Code { get; init; } + + public required string Name { get; init; } +} diff --git a/PersonalisationGroups/Api/Models/CriteriaDto.cs b/PersonalisationGroups/Api/Models/CriteriaDto.cs new file mode 100644 index 0000000..9df1f87 --- /dev/null +++ b/PersonalisationGroups/Api/Models/CriteriaDto.cs @@ -0,0 +1,10 @@ +namespace Our.Umbraco.PersonalisationGroups.Api.Models; + +public class CriteriaDto +{ + public required string Name { get; init; } + + public required string Alias { get; init; } + + public required string Description { get; init; } +} diff --git a/PersonalisationGroups/Api/Models/MemberGroupDto.cs b/PersonalisationGroups/Api/Models/MemberGroupDto.cs new file mode 100644 index 0000000..8a08f8d --- /dev/null +++ b/PersonalisationGroups/Api/Models/MemberGroupDto.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Api.Models; + +public class MemberGroupDto +{ + public required string Name { get; init; } +} diff --git a/PersonalisationGroups/Api/Models/MemberProfileFieldDto.cs b/PersonalisationGroups/Api/Models/MemberProfileFieldDto.cs new file mode 100644 index 0000000..11e0fca --- /dev/null +++ b/PersonalisationGroups/Api/Models/MemberProfileFieldDto.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Api.Models; + +public class MemberProfileFieldDto +{ + public required string Alias { get; init; } + + public required string Name { get; init; } +} diff --git a/PersonalisationGroups/Api/Models/MemberTypeDto.cs b/PersonalisationGroups/Api/Models/MemberTypeDto.cs new file mode 100644 index 0000000..0d6549b --- /dev/null +++ b/PersonalisationGroups/Api/Models/MemberTypeDto.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Api.Models; + +public class MemberTypeDto +{ + public required string Alias { get; init; } + + public required string Name { get; init; } +} diff --git a/PersonalisationGroups/Api/Models/RegionDto.cs b/PersonalisationGroups/Api/Models/RegionDto.cs new file mode 100644 index 0000000..a628fab --- /dev/null +++ b/PersonalisationGroups/Api/Models/RegionDto.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Api.Models; + +public class RegionDto +{ + public required string Code { get; init; } + + public required string Name { get; init; } +} diff --git a/PersonalisationGroups/AppConstants.cs b/PersonalisationGroups/AppConstants.cs new file mode 100644 index 0000000..44e281d --- /dev/null +++ b/PersonalisationGroups/AppConstants.cs @@ -0,0 +1,81 @@ +namespace Our.Umbraco.PersonalisationGroups; + +public enum Comparison +{ + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual, +} + +public static class AppConstants +{ + public const string PackageName = "Personalisation Groups"; + + public const string DefaultGroupPickerAlias = "personalisationGroups"; + + public const string PersonalisationGroupDefinitionPropertyEditorAlias = "PersonalisationGroups.GroupDefinition"; + + public const string PersonalisationGroupDefinitionPropertyAlias = "definition"; + + public const string CommonAssemblyName = "Our.Umbraco.PersonalisationGroups"; + + public const string DefaultGeoLocationCountryDatabasePath = "/umbraco/Data/PersonalisationGroups/GeoLite2-Country.mmdb"; + + public const string DefaultGeoLocationCityDatabasePath = "/umbraco/Data/PersonalisationGroups/GeoLite2-City.mmdb"; + + public const int DefaultNumberOfVisitsTrackingCookieExpiryInDays = 90; + + public const int DefaultViewedPagesTrackingCookieExpiryInDays = 90; + + public const int DefaultPersistentMatchedGroupsCookieExpiryInDays = 90; + + public const string DefaultCookieKeyForTrackingNumberOfVisits = "personalisationGroupsNumberOfVisits"; + + public const string DefaultCookieKeyForTrackingIfSessionAlreadyTracked = "personalisationGroupsNumberOfVisitsSessionStarted"; + + public const string DefaultCookieKeyForTrackingPagesViewed = "personalisationGroupsPagesViewed"; + + public const string DefaultCookieKeyForSessionMatchedGroups = "sessionMatchedGroups"; + + public const string DefaultCookieKeyForPersistentMatchedGroups = "persistentMatchedGroups"; + + public const string DefaultCookieKeyForTrackingCookiesDeclined = "personalisationGroupsCookiesDeclined"; + + public const string DefaultSessionKeyForTrackingCookiesDeclined = "PersonalisationGroups_CookiesDeclined"; + + public const string DefaultCdnCountryCodeHttpHeaderName = "CF-IPCountry"; + + public static class DocumentTypeAliases + { + public const string PersonalisationGroupsFolder = "PersonalisationGroupsFolder"; + + public const string PersonalisationGroup = "PersonalisationGroup"; + } + + public static class CacheKeys + { + public const string PersonalisationGroupsVisitorHash = "PersonalisationGroups.VisitorHash"; + } + + public static class SessionKeys + { + public const string PersonalisationGroupsEnsureSession = "PersonalisationGroups.EnsureSession"; + } + + public static class System + { + public const string MigrationPlanName = "PersonalisationGroups"; + } + + public static class ManagementApi + { + public const string RootPath = "personalisation-groups/management/api"; + + public const string ApiTitle = "Personalisation Groups Management API"; + + public const string ApiName = "personalisation-groups-management"; + + public const string GroupName = "Personalisation Groups"; + } +} diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.editor.controller.js deleted file mode 100644 index d4e418a..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.editor.controller.js +++ /dev/null @@ -1,25 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.AuthenticationStatusPersonalisationGroupCriteriaController", - function ($scope) { - - var definition = $scope.model.definition; - - $scope.renderModel = {}; - - if (definition) { - var authenticationStatus = JSON.parse(definition); - $scope.renderModel = authenticationStatus; - } - - $scope.saveAndClose = function () { - var value = $scope.renderModel.isAuthenticated ? true : false; - var serializedResult = "{ \"isAuthenticated\": " + value + " }"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.editor.html deleted file mode 100644 index 52eb6c0..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.editor.html +++ /dev/null @@ -1,33 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.translator.js deleted file mode 100644 index dce5e85..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.translator.js +++ /dev/null @@ -1,17 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.AuthenticationStatusTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var authenticationStatusDetails = JSON.parse(definition); - translation = authenticationStatusDetails.isAuthenticated ? "Is logged in." : "Is not logged in."; - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.editor.controller.js deleted file mode 100644 index 0f63d11..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.editor.controller.js +++ /dev/null @@ -1,109 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.ContinentPersonalisationGroupCriteriaController", - function ($scope, geoLocationService) { - - var definition = $scope.model.definition; - - function initAvailableContinentsList() { - geoLocationService.getContinentList() - .then(function (result) { - $scope.availableCountries = result.data; - }); - }; - - initAvailableContinentsList(); - - function resetNewContinent() { - $scope.newContinent = { code: "", hasError: false }; - } - - $scope.renderModel = { match: "IsLocatedIn" }; - $scope.renderModel.continents = []; - - if (definition) { - var continentSettings = JSON.parse(definition); - $scope.renderModel.match = continentSettings.match; - if (continentSettings.codes) { - for (var i = 0; i < continentSettings.codes.length; i++) { - $scope.renderModel.continents.push({ code: continentSettings.codes[i], edit: false }); - } - } - } - - resetNewContinent(); - - $scope.geoDetailsRequired = function () { - return $scope.renderModel.match !== "CouldNotBeLocated"; - }; - - $scope.getContinentName = function (code) { - return geoLocationService.getContinentName(code, $scope.availableCountries); - } - - $scope.edit = function (index) { - for (var i = 0; i < $scope.renderModel.continents.length; i++) { - $scope.renderModel.continents[i].edit = false; - } - - $scope.renderModel.continents[index].edit = true; - }; - - $scope.saveEdit = function (index) { - $scope.renderModel.continents[index].edit = false; - }; - - $scope.delete = function (index) { - $scope.renderModel.continents.splice(index, 1); - }; - - function isValidContinentCode(code) { - return code.length === 2; - }; - - $scope.add = function () { - if (isValidContinentCode($scope.newContinent.code)) { - var country = { code: $scope.newContinent.code, edit: false }; - $scope.renderModel.continents.push(country); - resetNewContinent(); - } else { - $scope.newContinent.hasError = true; - } - }; - - $scope.saveAndClose = function () { - - var serializedResult = "{ \"match\": \"" + $scope.renderModel.match + "\" "; - - if ($scope.renderModel.match !== "CouldNotBeLocated") { - - serializedResult += ", \"codes\": ["; - for (var i = 0; i < $scope.renderModel.continents.length; i++) { - if (i > 0) { - serializedResult += ", "; - } - - serializedResult += "\"" + $scope.renderModel.continents[i].code + "\""; - } - serializedResult += "], "; - - serializedResult += "\"names\": ["; - for (var i = 0; i < $scope.renderModel.continents.length; i++) { - if (i > 0) { - serializedResult += ", "; - } - - serializedResult += "\"" + geoLocationService.getContinentName($scope.renderModel.continents[i].code, $scope.availableCountries) + "\""; - } - - serializedResult += "]"; - } - - serializedResult += " }"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.editor.html deleted file mode 100644 index 4228bef..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.editor.html +++ /dev/null @@ -1,77 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.translator.js deleted file mode 100644 index d687c0a..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.translator.js +++ /dev/null @@ -1,45 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.ContinentTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedContinentDetails = JSON.parse(definition); - if (selectedContinentDetails.match === "CouldNotBeLocated") { - translation = "Visitor cannot be located"; - } else { - translation = "Visitor is "; - switch (selectedContinentDetails.match) { - case "IsLocatedIn": - translation += "located"; - break; - case "IsNotLocatedIn": - translation += "not located"; - break; - } - - translation += " in: "; - - for (var i = 0; i < selectedContinentDetails.codes.length; i++) { - if (i > 0 && i === selectedContinentDetails.codes.length - 1) { - translation += " or "; - } else if (i > 0) { - translation += ", "; - } - - // Versions 0.2.5 and later store the continent name, before that we just had the code. - // So display the name if we have it, otherwise just the code. - translation += selectedContinentDetails.names - ? selectedContinentDetails.names[i] - : selectedContinentDetails.codes[i].toUpperCase(); - } - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.editor.controller.js deleted file mode 100644 index eda7d15..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.editor.controller.js +++ /dev/null @@ -1,32 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.CookiePersonalisationGroupCriteriaController", - function ($scope) { - - var definition = $scope.model.definition; - - $scope.renderModel = { match: "Exists" }; - - if (definition) { - var cookieSettings = JSON.parse(definition); - $scope.renderModel = cookieSettings; - } - - $scope.valueRequired = function() { - return !($scope.renderModel.match === "Exists" || $scope.renderModel.match === "DoesNotExist"); - }; - - $scope.saveAndClose = function () { - if ($scope.renderModel.key) { - var serializedResult = "{ \"key\": \"" + $scope.renderModel.key + "\", " + - "\"match\": \"" + $scope.renderModel.match + "\", " + - "\"value\": \"" + ($scope.valueRequired() ? $scope.renderModel.value : "") + "\" }"; - - $scope.model.submit(serializedResult); - } - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.editor.html deleted file mode 100644 index d5349c4..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.editor.html +++ /dev/null @@ -1,57 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.translator.js deleted file mode 100644 index 47c854e..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.translator.js +++ /dev/null @@ -1,49 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.CookieTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedCookieDetails = JSON.parse(definition); - translation = "Cookie with key '" + selectedCookieDetails.key + "' "; - switch (selectedCookieDetails.match) { - case "Exists": - translation += "is present."; - break; - case "DoesNotExist": - translation += "is absent."; - break; - case "MatchesValue": - translation += "matches value '" + selectedCookieDetails.value + "'."; - break; - case "ContainsValue": - translation += "contains value '" + selectedCookieDetails.value + "'."; - break; - case "GreaterThanValue": - translation += "is greater than value '" + selectedCookieDetails.value + "'."; - break; - case "GreaterThanOrEqualToValue": - translation += "is greater than or equal to value '" + selectedCookieDetails.value + "'."; - break; - case "LessThanValue": - translation += "is less than value '" + selectedCookieDetails.value + "'."; - break; - case "LessThanOrEqualToValue": - translation += "is less than or equal to value '" + selectedCookieDetails.value + "'."; - break; - case "MatchesRegex": - translation += "matches regular expression '" + selectedCookieDetails.value + "'."; - break; - case "DoesNotMatchRegex": - translation += "does not match regular expression '" + selectedCookieDetails.value + "'."; - break; - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/definition.editor.controller.js deleted file mode 100644 index 7f80445..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/definition.editor.controller.js +++ /dev/null @@ -1,109 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.CountryPersonalisationGroupCriteriaController", - function ($scope, geoLocationService) { - - var definition = $scope.model.definition; - - function initAvailableCountriesList() { - geoLocationService.getCountryList() - .then(function (result) { - $scope.availableCountries = result.data; - }); - }; - - initAvailableCountriesList(); - - function resetNewCountry() { - $scope.newCountry = { code: "", hasError: false }; - } - - $scope.renderModel = { match: "IsLocatedIn" }; - $scope.renderModel.countries = []; - - if (definition) { - var countrySettings = JSON.parse(definition); - $scope.renderModel.match = countrySettings.match; - if (countrySettings.codes) { - for (var i = 0; i < countrySettings.codes.length; i++) { - $scope.renderModel.countries.push({ code: countrySettings.codes[i], edit: false }); - } - } - } - - resetNewCountry(); - - $scope.geoDetailsRequired = function () { - return $scope.renderModel.match !== "CouldNotBeLocated"; - }; - - $scope.getCountryName = function (code) { - return geoLocationService.getCountryName(code, $scope.availableCountries); - } - - $scope.edit = function (index) { - for (var i = 0; i < $scope.renderModel.countries.length; i++) { - $scope.renderModel.countries[i].edit = false; - } - - $scope.renderModel.countries[index].edit = true; - }; - - $scope.saveEdit = function (index) { - $scope.renderModel.countries[index].edit = false; - }; - - $scope.delete = function (index) { - $scope.renderModel.countries.splice(index, 1); - }; - - function isValidCountryCode(code) { - return code.length === 2; - }; - - $scope.add = function () { - if (isValidCountryCode($scope.newCountry.code)) { - var country = { code: $scope.newCountry.code, edit: false }; - $scope.renderModel.countries.push(country); - resetNewCountry(); - } else { - $scope.newCountry.hasError = true; - } - }; - - $scope.saveAndClose = function () { - - var serializedResult = "{ \"match\": \"" + $scope.renderModel.match + "\" "; - - if ($scope.renderModel.match !== "CouldNotBeLocated") { - - serializedResult += ", \"codes\": ["; - for (var i = 0; i < $scope.renderModel.countries.length; i++) { - if (i > 0) { - serializedResult += ", "; - } - - serializedResult += "\"" + $scope.renderModel.countries[i].code + "\""; - } - serializedResult += "], "; - - serializedResult += "\"names\": ["; - for (var i = 0; i < $scope.renderModel.countries.length; i++) { - if (i > 0) { - serializedResult += ", "; - } - - serializedResult += "\"" + geoLocationService.getCountryName($scope.renderModel.countries[i].code, $scope.availableCountries) + "\""; - } - - serializedResult += "]"; - } - - serializedResult += " }"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/definition.editor.html deleted file mode 100644 index bae1a8e..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/definition.editor.html +++ /dev/null @@ -1,77 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/definition.translator.js deleted file mode 100644 index 07b1120..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/definition.translator.js +++ /dev/null @@ -1,45 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.CountryTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedCountryDetails = JSON.parse(definition); - if (selectedCountryDetails.match === "CouldNotBeLocated") { - translation = "Visitor cannot be located"; - } else { - translation = "Visitor is "; - switch (selectedCountryDetails.match) { - case "IsLocatedIn": - translation += "located"; - break; - case "IsNotLocatedIn": - translation += "not located"; - break; - } - - translation += " in: "; - - for (var i = 0; i < selectedCountryDetails.codes.length; i++) { - if (i > 0 && i === selectedCountryDetails.codes.length - 1) { - translation += " or "; - } else if (i > 0) { - translation += ", "; - } - - // Versions 0.2.5 and later store the country name, before that we just had the code. - // So display the name if we have it, otherwise just the code. - translation += selectedCountryDetails.names - ? selectedCountryDetails.names[i] - : selectedCountryDetails.codes[i].toUpperCase(); - } - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/geolocation.service.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/geolocation.service.js deleted file mode 100644 index 67dbc47..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Country/geolocation.service.js +++ /dev/null @@ -1,58 +0,0 @@ -angular.module("umbraco.services") - .factory("geoLocationService", function ($http) { - - var service = { - getContinentList: function () { - var url = "/App_Plugins/PersonalisationGroups/GeoLocation/GetContinents"; - return $http.get(url, { cache: true }); - }, - - getCountryList: function (withRegionsOnly) { - var url = "/App_Plugins/PersonalisationGroups/GeoLocation/GetCountries?withRegionsOnly=" + withRegionsOnly; - return $http.get(url, { cache: true }); - }, - - getRegionList: function (countryCode) { - var url = "/App_Plugins/PersonalisationGroups/GeoLocation/GetRegions?countryCode=" + countryCode; - return $http.get(url, { cache: true }); - }, - - getContinentName: function (code, continents) { - if (continents) { - for (var j = 0; j < continents.length; j++) { - if (continents[j].code === code) { - return continents[j].name; - } - } - } - - return ""; - }, - - getCountryName: function (code, countries) { - if (countries) { - for (var j = 0; j < countries.length; j++) { - if (countries[j].code === code) { - return countries[j].name; - } - } - } - - return ""; - }, - - getRegionName: function (code, regions) { - if (regions) { - for (var j = 0; j < regions.length; j++) { - if (regions[j].code === code) { - return regions[j].name; - } - } - } - - return ""; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.editor.controller.js deleted file mode 100644 index dadd249..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.editor.controller.js +++ /dev/null @@ -1,43 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.DayOfWeekPersonalisationGroupCriteriaController", - function ($scope) { - - var daysInWeek = 7; - - var definition = $scope.model.definition; - - $scope.renderModel = {}; - $scope.renderModel.days = []; - for (var i = 0; i < daysInWeek; i++) { - $scope.renderModel.days.push(false); - } - - if (definition) { - var selectedDays = JSON.parse(definition); - for (var i = 0; i < selectedDays.length; i++) { - $scope.renderModel.days[selectedDays[i] - 1] = true; - } - } - - $scope.saveAndClose = function () { - var serializedResult = "["; - for (var i = 0; i < daysInWeek; i++) { - if ($scope.renderModel.days[i]) { - if (serializedResult.length > 1) { - serializedResult += ","; - } - - serializedResult += (i + 1); - } - } - - serializedResult += "]"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.editor.html deleted file mode 100644 index 190ade4..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.editor.html +++ /dev/null @@ -1,34 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.translator.js deleted file mode 100644 index a4dd5bb..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.translator.js +++ /dev/null @@ -1,25 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.DayOfWeekTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; - var selectedDays = JSON.parse(definition); - - for (var i = 0; i < selectedDays.length; i++) { - if (translation.length > 0) { - translation += ", "; - } - - translation += days[selectedDays[i] - 1]; - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Host/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Host/definition.editor.controller.js deleted file mode 100644 index 1eb3cfa..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Host/definition.editor.controller.js +++ /dev/null @@ -1,25 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.HostPersonalisationGroupCriteriaController", - function ($scope) { - - var definition = $scope.model.definition; - - $scope.renderModel = { match: "MatchesValue" }; - - if (definition) { - var hostSettings = JSON.parse(definition); - $scope.renderModel = hostSettings; - } - - $scope.saveAndClose = function () { - var serializedResult = "{ \"value\": \"" + $scope.renderModel.value + "\", " + - "\"match\": \"" + $scope.renderModel.match + "\" }"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Host/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Host/definition.editor.html deleted file mode 100644 index 2bef3b6..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Host/definition.editor.html +++ /dev/null @@ -1,45 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Host/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Host/definition.translator.js deleted file mode 100644 index 543d670..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Host/definition.translator.js +++ /dev/null @@ -1,31 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.HostTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedHostDetails = JSON.parse(definition); - translation = "Host value "; - switch (selectedHostDetails.match) { - case "MatchesValue": - translation += "matches '" + selectedHostDetails.value + "'."; - break; - case "DoesNotMatchValue": - translation += "does not match '" + selectedHostDetails.value + "'."; - break; - case "ContainsValue": - translation += "contains '" + selectedHostDetails.value + "'."; - break; - case "DoesNotContainValue": - translation += "does not contain '" + selectedHostDetails.value + "'."; - break; - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.editor.controller.js deleted file mode 100644 index 14c5513..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.editor.controller.js +++ /dev/null @@ -1,39 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.MemberGroupPersonalisationGroupCriteriaController", - function ($scope, $http) { - - var definition = $scope.model.definition; - - function initGroupList() { - $scope.availableGroups = []; - $http.get("/App_Plugins/PersonalisationGroups/Member/GetMemberGroups") - .then(function (result) { - $scope.availableGroups = result.data; - console.log($scope.availableGroups); - if (result.data.length > 0 && !$scope.renderModel.groupName) { - $scope.renderModel.groupName = result.data[0]; - } - }); - }; - - $scope.renderModel = { match: "IsInGroup" }; - - initGroupList(); - - if (definition) { - var memberGroupSettings = JSON.parse(definition); - $scope.renderModel = memberGroupSettings; - } - - $scope.saveAndClose = function () { - var serializedResult = "{ \"groupName\": \"" + $scope.renderModel.groupName + "\", " + - "\"match\": \"" + $scope.renderModel.match + "\" }"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.editor.html deleted file mode 100644 index 9a4009f..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.editor.html +++ /dev/null @@ -1,44 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.translator.js deleted file mode 100644 index 7dcf524..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.translator.js +++ /dev/null @@ -1,19 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.MemberGroupTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var memberTypeDetails = JSON.parse(definition); - translation = (memberTypeDetails.match === "IsInGroup" ? "Is in group " : "Is not in group ") + - "'" + memberTypeDetails.groupName + "'."; - - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.editor.controller.js deleted file mode 100644 index 22847f3..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.editor.controller.js +++ /dev/null @@ -1,39 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.MemberProfileFieldPersonalisationGroupCriteriaController", - function ($scope, $http) { - - var definition = $scope.model.definition; - - function initGroupList() { - $scope.availableFields = []; - $http.get("/App_Plugins/PersonalisationGroups/Member/GetMemberProfileFields") - .then(function (result) { - $scope.availableFields = result.data; - if (result.data.length > 0 && !$scope.renderModel.alias) { - $scope.renderModel.alias = result.data[0]; - } - }); - }; - - $scope.renderModel = { match: "MatchesValue" }; - - initGroupList(); - - if (definition) { - var profileFieldSettings = JSON.parse(definition); - $scope.renderModel = profileFieldSettings; - } - - $scope.saveAndClose = function () { - var serializedResult = "{ \"alias\": \"" + $scope.renderModel.alias + "\", " + - "\"match\": \"" + $scope.renderModel.match + "\", " + - "\"value\": \"" + $scope.renderModel.value + "\" }"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.editor.html deleted file mode 100644 index 3b84f5f..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.editor.html +++ /dev/null @@ -1,54 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.translator.js deleted file mode 100644 index 37d5465..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.translator.js +++ /dev/null @@ -1,37 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.MemberProfileFieldTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var profileFieldDetails = JSON.parse(definition); - translation = "Field with alias '" + profileFieldDetails.alias + "' "; - switch (profileFieldDetails.match) { - case "MatchesValue": - translation += "matches value '" + profileFieldDetails.value + "'."; - break; - case "ContainsValue": - translation += "contains value '" + profileFieldDetails.value + "'."; - break; - case "GreaterThanValue": - translation += "is greater than value '" + profileFieldDetails.value + "'."; - break; - case "GreaterThanOrEqualToValue": - translation += "is greater than or equal to value '" + profileFieldDetails.value + "'."; - break; - case "LessThanValue": - translation += "is less than value '" + profileFieldDetails.value + "'."; - break; - case "LessThanOrEqualToValue": - translation += "is less than or equal to value '" + profileFieldDetails.value + "'."; - break; - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.editor.controller.js deleted file mode 100644 index 4572bbc..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.editor.controller.js +++ /dev/null @@ -1,38 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.MemberTypePersonalisationGroupCriteriaController", - function ($scope, $http) { - - var definition = $scope.model.definition; - - function initGroupList() { - $scope.availableTypes = []; - $http.get("/App_Plugins/PersonalisationGroups/Member/GetMemberTypes") - .then(function (result) { - $scope.availableTypes = result.data; - if (result.data.length > 0 && !$scope.renderModel.typeName) { - $scope.renderModel.typeName = result.data[0]; - } - }); - }; - - $scope.renderModel = { match: "IsOfType" }; - - initGroupList(); - - if (definition) { - var memberTypeSettings = JSON.parse(definition); - $scope.renderModel = memberTypeSettings; - } - - $scope.saveAndClose = function () { - var serializedResult = "{ \"typeName\": \"" + $scope.renderModel.typeName + "\", " + - "\"match\": \"" + $scope.renderModel.match + "\" }"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.editor.html deleted file mode 100644 index f92110e..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.editor.html +++ /dev/null @@ -1,44 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.translator.js deleted file mode 100644 index 59df913..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.translator.js +++ /dev/null @@ -1,19 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.MemberTypeTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var memberTypeDetails = JSON.parse(definition); - translation = (memberTypeDetails.match === "IsOfType" ? "Is of type " : "Is not of type ") + - "'" + memberTypeDetails.typeName + "'."; - - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.editor.controller.js deleted file mode 100644 index 95a5634..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.editor.controller.js +++ /dev/null @@ -1,43 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.MonthOfYearPersonalisationGroupCriteriaController", - function ($scope) { - - var monthsInYear = 12; - - var definition = $scope.model.definition; - - $scope.renderModel = {}; - $scope.renderModel.months = []; - for (var i = 0; i < monthsInYear; i++) { - $scope.renderModel.months.push(false); - } - - if (definition) { - var selectedMonths = JSON.parse(definition); - for (var i = 0; i < selectedMonths.length; i++) { - $scope.renderModel.months[selectedMonths[i] - 1] = true; - } - } - - $scope.saveAndClose = function () { - var serializedResult = "["; - for (var i = 0; i < monthsInYear; i++) { - if ($scope.renderModel.months[i]) { - if (serializedResult.length > 1) { - serializedResult += ","; - } - - serializedResult += (i + 1); - } - } - - serializedResult += "]"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.editor.html deleted file mode 100644 index 250bcbb..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.editor.html +++ /dev/null @@ -1,39 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.translator.js deleted file mode 100644 index c33e7ae..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.translator.js +++ /dev/null @@ -1,25 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.MonthOfYearTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; - var selectedMonths = JSON.parse(definition); - - for (var i = 0; i < selectedMonths.length; i++) { - if (translation.length > 0) { - translation += ", "; - } - - translation += months[selectedMonths[i] - 1]; - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.editor.controller.js deleted file mode 100644 index a3ae1d8..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.editor.controller.js +++ /dev/null @@ -1,25 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.NumberOfVisitsPersonalisationGroupCriteriaController", - function ($scope) { - - var definition = $scope.model.definition; - - $scope.renderModel = { match: "Exists" }; - - if (definition) { - var numberOfVisitsSettings = JSON.parse(definition); - $scope.renderModel = numberOfVisitsSettings; - } - - $scope.saveAndClose = function () { - var serializedResult = "{ \"match\": \"" + $scope.renderModel.match + "\", " + - "\"number\": " + $scope.renderModel.number + "}"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.editor.html deleted file mode 100644 index 21f80d7..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.editor.html +++ /dev/null @@ -1,44 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.translator.js deleted file mode 100644 index 3f668e4..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.translator.js +++ /dev/null @@ -1,31 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.NumberOfVisitsTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedNumberOfVisitsDetails = JSON.parse(definition); - translation = "Visitor has visited the site "; - switch (selectedNumberOfVisitsDetails.match) { - case "MoreThan": - translation += "more than"; - break; - case "LessThan": - translation += "less than"; - break; - case "Exactly": - translation += "exactly"; - break; - } - - translation += " " + selectedNumberOfVisitsDetails.number + - " time" + (selectedNumberOfVisitsDetails.number === 1 ? "" : "s"); - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.editor.controller.js deleted file mode 100644 index 3fe54a6..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.editor.controller.js +++ /dev/null @@ -1,105 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.PagesViewedPersonalisationGroupCriteriaController", - function ($scope, $injector, editorService, entityResource, iconHelper) { - - var definition = $scope.model.definition; - - function loadNodeDetails() { - - $scope.renderModel.nodes = []; - entityResource.getByIds($scope.renderModel.nodeIds, "Document").then(function (data) { - - // Load full node details from the ids that were stored, in the same order - _.each($scope.renderModel.nodeIds, function (id, i) { - var entity = _.find(data, function (d) { - return d.id == id; - }); - - if (entity) { - entity.icon = iconHelper.convertFromLegacyIcon(entity.icon); - $scope.renderModel.nodes.push({ name: entity.name, id: entity.id, icon: entity.icon }); - } - - }); - - }); - } - - $scope.renderModel = { match: "ViewedAny", nodes: [] }; - - if (definition) { - var pagesViewedSettings = JSON.parse(definition); - $scope.renderModel = pagesViewedSettings; - if ($scope.renderModel.nodeIds.length > 0) { - loadNodeDetails(); - } - } - - function processSelections(selection) { - if (angular.isArray(selection)) { - _.each(selection, - function(item) { - $scope.add(item); - }); - } else { - $scope.clear(); - $scope.add(data); - } - } - - $scope.openContentPicker = function () { - - var dialogOptions = { - view: "views/common/infiniteeditors/treepicker/treepicker.html", - size: "small", - section: "content", - treeAlias: "content", - multiPicker: true, - submit: function (data) { - processSelections(data.selection); - editorService.close(); - }, - close: function () { - editorService.close(); - } - }; - editorService.contentPicker(dialogOptions); - }; - - $scope.remove = function (index) { - $scope.renderModel.nodes.splice(index, 1); - }; - - $scope.add = function (item) { - var currIds = _.map($scope.renderModel.nodes, function (i) { - return i.id; - }); - - if (currIds.indexOf(item.id) < 0) { - item.icon = iconHelper.convertFromLegacyIcon(item.icon); - $scope.renderModel.nodes.push({ name: item.name, id: item.id, icon: item.icon }); - } - }; - - $scope.clear = function () { - $scope.renderModel.nodes = []; - }; - - $scope.saveAndClose = function () { - - // Populate nodeIds property that is saved to the definition from the list of selected nodes - $scope.renderModel.nodeIds = _.map($scope.renderModel.nodes, function (i) { - return i.id; - }); - - var serializedResult = "{ \"match\": \"" + $scope.renderModel.match + "\", " + - "\"nodeIds\": " + "[" + $scope.renderModel.nodeIds.join() + "]" + " }"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.editor.html deleted file mode 100644 index 97405f1..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.editor.html +++ /dev/null @@ -1,64 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.translator.js deleted file mode 100644 index 858b8ca..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.translator.js +++ /dev/null @@ -1,34 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.PagesViewedTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedPagesViewedDetails = JSON.parse(definition); - translation = "Visitor has "; - switch (selectedPagesViewedDetails.match) { - case "ViewedAny": - translation += "viewed any of"; - break; - case "ViewedAll": - translation += "viewed all"; - break; - case "NotViewedAny": - translation += "not viewed any of"; - break; - case "NotViewedAll": - translation += "not viewed all"; - break; - } - - translation += " the " + selectedPagesViewedDetails.nodeIds.length + - " selected page" + (selectedPagesViewedDetails.nodeIds.length === 1 ? "" : "s"); - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.editor.controller.js deleted file mode 100644 index f1ca8c0..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.editor.controller.js +++ /dev/null @@ -1,32 +0,0 @@ -angular.module("umbraco") -.controller("PersonalisationGroups.QuerystringPersonalisationGroupCriteriaController", - function ($scope) { - - var definition = $scope.model.definition; - - $scope.renderModel = { match: "MatchesValue" }; - - $scope.currentMatchIsCaseInsensitive = function() { - if (!$scope.renderModel.match) { - return false; - } - - var key = $scope.renderModel.match; - return key.indexOf('Regex') === -1; - } - - if (definition) { - $scope.renderModel = JSON.parse(definition); - } - - $scope.saveAndClose = function () { - - var serializedResult = JSON.stringify($scope.renderModel); - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.editor.html deleted file mode 100644 index 9e2ca50..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.editor.html +++ /dev/null @@ -1,54 +0,0 @@ -
- - - - -
diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.translator.js deleted file mode 100644 index 01a0a53..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.translator.js +++ /dev/null @@ -1,37 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.QuerystringTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedQuerystringDetails = JSON.parse(definition); - translation = "Key of '" + selectedQuerystringDetails.key + "' "; - switch (selectedQuerystringDetails.match) { - case "MatchesValue": - translation += "matches '" + selectedQuerystringDetails.value + "'."; - break; - case "DoesNotMatchValue": - translation += "does not match '" + selectedQuerystringDetails.value + "'."; - break; - case "ContainsValue": - translation += "contains '" + selectedQuerystringDetails.value + "'."; - break; - case "DoesNotContainValue": - translation += "does not contain '" + selectedQuerystringDetails.value + "'."; - break; - case "MatchesRegex": - translation += "matches regular expression '" + selectedQuerystringDetails.value + "'."; - break; - case "DoesNotMatchRegex": - translation += "does not match regular expression '" + selectedQuerystringDetails.value + "'."; - break; - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.editor.controller.js deleted file mode 100644 index 4776186..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.editor.controller.js +++ /dev/null @@ -1,25 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.ReferralPersonalisationGroupCriteriaController", - function ($scope) { - - var definition = $scope.model.definition; - - $scope.renderModel = { match: "MatchesValue" }; - - if (definition) { - var referrerSettings = JSON.parse(definition); - $scope.renderModel = referrerSettings; - } - - $scope.saveAndClose = function () { - var serializedResult = "{ \"value\": \"" + $scope.renderModel.value + "\", " + - "\"match\": \"" + $scope.renderModel.match + "\" }"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.editor.html deleted file mode 100644 index d98f87d..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.editor.html +++ /dev/null @@ -1,45 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.translator.js deleted file mode 100644 index 1ae0767..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.translator.js +++ /dev/null @@ -1,31 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.ReferralTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedReferrerDetails = JSON.parse(definition); - translation = "Referrer value "; - switch (selectedReferrerDetails.match) { - case "MatchesValue": - translation += "matches '" + selectedReferrerDetails.value + "'."; - break; - case "DoesNotMatchValue": - translation += "does not match '" + selectedReferrerDetails.value + "'."; - break; - case "ContainsValue": - translation += "contains '" + selectedReferrerDetails.value + "'."; - break; - case "DoesNotContainValue": - translation += "does not contain '" + selectedReferrerDetails.value + "'."; - break; - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Region/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Region/definition.editor.controller.js deleted file mode 100644 index 1bae5ae..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Region/definition.editor.controller.js +++ /dev/null @@ -1,112 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.RegionPersonalisationGroupCriteriaController", - function ($scope, geoLocationService) { - - var definition = $scope.model.definition; - - var defaultCountryCode = "GB"; - - function initAvailableRegionsList(countryCode) { - geoLocationService.getRegionList(countryCode) - .then(function (result) { - $scope.availableRegions = result.data; - }); - } - - function initCountryList() { - geoLocationService.getCountryList(true) - .then(function (result) { - $scope.availableCountries = result.data; - }); - }; - - initCountryList(); - - function resetNewRegion() { - $scope.newRegion = { name: "", hasError: false }; - } - - function clearRegions() { - $scope.renderModel.regions = []; - } - - $scope.renderModel = { match: "IsLocatedIn", countryCode: defaultCountryCode }; - $scope.renderModel.regions = []; - - if (definition) { - var regionSettings = JSON.parse(definition); - $scope.renderModel.match = regionSettings.match; - $scope.renderModel.countryCode = regionSettings.countryCode; - if (regionSettings.names) { - for (var i = 0; i < regionSettings.names.length; i++) { - $scope.renderModel.regions.push({ name: regionSettings.names[i], edit: false }); - } - } - - initAvailableRegionsList(regionSettings.countryCode); - } else { - initAvailableRegionsList(defaultCountryCode); - } - - resetNewRegion(); - - $scope.geoDetailsRequired = function () { - return $scope.renderModel.match !== "CouldNotBeLocated"; - }; - - $scope.changedCountryCode = function (countryCode) { - clearRegions(); - initAvailableRegionsList(countryCode); - } - - $scope.edit = function (index) { - for (var i = 0; i < $scope.renderModel.regions.length; i++) { - $scope.renderModel.regions[i].edit = false; - } - - $scope.renderModel.regions[index].edit = true; - }; - - $scope.saveEdit = function (index) { - $scope.renderModel.regions[index].edit = false; - }; - - $scope.delete = function (index) { - $scope.renderModel.regions.splice(index, 1); - }; - - $scope.add = function () { - var country = { name: $scope.newRegion.name, edit: false }; - $scope.renderModel.regions.push(country); - resetNewRegion(); - }; - - $scope.saveAndClose = function () { - var serializedResult = "{ \"match\": \"" + $scope.renderModel.match + "\""; - - if ($scope.renderModel.match !== "CouldNotBeLocated") { - serializedResult += ", "; - serializedResult += "\"countryCode\": \"" + $scope.renderModel.countryCode + "\", "; - serializedResult += "\"countryName\": \"" + geoLocationService.getCountryName($scope.renderModel.countryCode, $scope.availableCountries) + "\", "; - - serializedResult += "\"names\": ["; - for (var i = 0; i < $scope.renderModel.regions.length; i++) { - if (i > 0) { - serializedResult += ", "; - } - - serializedResult += "\"" + $scope.renderModel.regions[i].name + "\""; - } - - serializedResult += "] "; - } - - serializedResult += " }"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Region/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Region/definition.editor.html deleted file mode 100644 index bcd524c..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Region/definition.editor.html +++ /dev/null @@ -1,87 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Region/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Region/definition.translator.js deleted file mode 100644 index f2db1f5..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Region/definition.translator.js +++ /dev/null @@ -1,43 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.RegionTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedRegionDetails = JSON.parse(definition); - if (selectedRegionDetails.match === "CouldNotBeLocated") { - translation = "Visitor cannot be located"; - } else { - translation = "Visitor is "; - switch (selectedRegionDetails.match) { - case "IsLocatedIn": - translation += "located"; - break; - case "IsNotLocatedIn": - translation += "not located"; - break; - } - - translation += " in: "; - - for (var i = 0; i < selectedRegionDetails.names.length; i++) { - if (i > 0 && i === selectedRegionDetails.names.length - 1) { - translation += " or "; - } else if (i > 0) { - translation += ", "; - } - - translation += selectedRegionDetails.names[i]; - } - - translation += ", " + selectedRegionDetails.countryName; - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Session/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Session/definition.editor.controller.js deleted file mode 100644 index 676693b..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Session/definition.editor.controller.js +++ /dev/null @@ -1,32 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.SessionPersonalisationGroupCriteriaController", - function ($scope) { - - var definition = $scope.model.definition; - - $scope.renderModel = { match: "Exists" }; - - if (definition) { - var sessionSettings = JSON.parse(definition); - $scope.renderModel = sessionSettings; - } - - $scope.valueRequired = function () { - return !($scope.renderModel.match === "Exists" || $scope.renderModel.match === "DoesNotExist"); - }; - - $scope.saveAndClose = function () { - if ($scope.renderModel.key) { - var serializedResult = "{ \"key\": \"" + $scope.renderModel.key + "\", " + - "\"match\": \"" + $scope.renderModel.match + "\", " + - "\"value\": \"" + ($scope.valueRequired() ? $scope.renderModel.value : "") + "\" }"; - - $scope.model.submit(serializedResult); - } - }; - - $scope.close = function () { - $scope.model.close(); - } - - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Session/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Session/definition.editor.html deleted file mode 100644 index 3af23a9..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Session/definition.editor.html +++ /dev/null @@ -1,57 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Session/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Session/definition.translator.js deleted file mode 100644 index c952783..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/Session/definition.translator.js +++ /dev/null @@ -1,49 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.SessionTranslatorService", function () { - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedSessionDetails = JSON.parse(definition); - translation = "Session key '" + selectedSessionDetails.key + "' "; - switch (selectedSessionDetails.match) { - case "Exists": - translation += "is present."; - break; - case "DoesNotExist": - translation += "is absent."; - break; - case "MatchesValue": - translation += "matches value '" + selectedSessionDetails.value + "'."; - break; - case "ContainsValue": - translation += "contains value '" + selectedSessionDetails.value + "'."; - break; - case "GreaterThanValue": - translation += "is greater than value '" + selectedSessionDetails.value + "'."; - break; - case "GreaterThanOrEqualToValue": - translation += "is greater than or equal to value '" + selectedSessionDetails.value + "'."; - break; - case "LessThanValue": - translation += "is less than value '" + selectedSessionDetails.value + "'."; - break; - case "LessThanOrEqualToValue": - translation += "is less than or equal to value '" + selectedSessionDetails.value + "'."; - break; - case "MatchesRegex": - translation += "matches regular expression '" + selectedSessionDetails.value + "'."; - break; - case "DoesNotMatchRegex": - translation += "does not match regular expression '" + selectedSessionDetails.value + "'."; - break; - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.editor.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.editor.controller.js deleted file mode 100644 index 5ec3d5c..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.editor.controller.js +++ /dev/null @@ -1,78 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.TimeOfDayPersonalisationGroupCriteriaController", - function ($scope) { - - var definition = $scope.model.definition; - - function isNumber(n) { - return !isNaN(parseFloat(n)) && isFinite(n); - }; - - function isValidPeriod(from, to) { - return isNumber(from) && isNumber(to) && - parseInt(from) >= 0 && parseInt(from) < 2359 && - parseInt(to) >= 0 && parseInt(to) < 2359 && - parseInt(to) > parseInt(from); - }; - - $scope.renderModel = {}; - $scope.renderModel.periods = []; - - if (definition) { - $scope.renderModel.periods = JSON.parse(definition); - } - - $scope.newFrom = ""; - $scope.newTo = ""; - $scope.currentEditPeriod = null; - $scope.hasError = false; - - $scope.edit = function (index) { - for (var i = 0; i < $scope.renderModel.periods.length; i++) { - $scope.renderModel.periods[i].edit = false; - } - - $scope.renderModel.periods[index].edit = true; - }; - - $scope.saveEdit = function (index) { - $scope.renderModel.periods[index].edit = false; - }; - - $scope.delete = function (index) { - $scope.renderModel.periods.splice(index, 1); - }; - - $scope.add = function () { - if (isValidPeriod($scope.newFrom, $scope.newTo)) { - var period = { from: $scope.newFrom, to: $scope.newTo }; - $scope.renderModel.periods.push(period); - - $scope.newFrom = ""; - $scope.newTo = ""; - } else { - $scope.hasError = true; - } - }; - - $scope.saveAndClose = function () { - var serializedResult = "["; - - for (var i = 0; i < $scope.renderModel.periods.length; i++) { - if (serializedResult.length > 1) { - serializedResult += ", "; - } - - serializedResult += "{ \"from\": \"" + $scope.renderModel.periods[i].from + "\", " + - "\"to\": \"" + $scope.renderModel.periods[i].to + "\" }"; - } - - serializedResult += "]"; - - $scope.model.submit(serializedResult); - }; - - $scope.close = function () { - $scope.model.close(); - } - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.editor.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.editor.html deleted file mode 100644 index b8ca3ae..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.editor.html +++ /dev/null @@ -1,63 +0,0 @@ -
- - - - -
- diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.translator.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.translator.js deleted file mode 100644 index 530deb1..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.translator.js +++ /dev/null @@ -1,28 +0,0 @@ -angular.module("umbraco.services") - .factory("PersonalisationGroups.TimeOfDayTranslatorService", function () { - - function formatTime(time) { - return time.substr(0, time.length - 2) + ":" + time.substr(time.length - 2); - } - - var service = { - translate: function (definition) { - var translation = ""; - if (definition) { - var selectedTimes = JSON.parse(definition); - - for (var i = 0; i < selectedTimes.length; i++) { - if (translation.length > 0) { - translation += ", "; - } - - translation += formatTime(selectedTimes[i].from) + " - " + formatTime(selectedTimes[i].to); - } - } - - return translation; - } - }; - - return service; - }); \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/package.manifest b/PersonalisationGroups/App_Plugins/PersonalisationGroups/package.manifest deleted file mode 100644 index ded7e4f..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/package.manifest +++ /dev/null @@ -1,60 +0,0 @@ -{ - "javascript": [ - "~/App_Plugins/PersonalisationGroups/personalisaton-group-definition.controller.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/AuthenticationStatus/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/Cookie/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/Continent/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/Country/geolocation.service.js", - "~/App_Plugins/PersonalisationGroups/Criteria/Country/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/Country/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/DayOfWeek/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/Host/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/Host/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/MemberGroup/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/MemberProfileField/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/MemberType/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/MonthOfYear/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/NumberOfVisits/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/PagesViewed/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/Querystring/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/Referral/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/Region/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/Region/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/Session/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/Session/definition.translator.js", - - "~/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.editor.controller.js", - "~/App_Plugins/PersonalisationGroups/Criteria/TimeOfDay/definition.translator.js" - ], - "css": [ - "~/App_Plugins/PersonalisationGroups/personalisation-groups.css" - ] -} \ No newline at end of file diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisation-group-definition.html b/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisation-group-definition.html deleted file mode 100644 index 244fc18..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisation-group-definition.html +++ /dev/null @@ -1,65 +0,0 @@ -
- - - - - - - -
- Determines for how long a user that is matched to a personalisation group remains in it -
-
- - - -
- A number between 1 and 100, can be used to weight groups when scoring the visitor's match to a piece of content -
-
- -
-
- -
- - -
- -
-
-
-
- - - - - - - - - - - - - - - - -
CriteriaDefinition
- - - - -
-
diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisation-groups.css b/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisation-groups.css deleted file mode 100644 index f477c41..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisation-groups.css +++ /dev/null @@ -1,17 +0,0 @@ -.personalisation-group-definition { - padding-top: 6px; -} -.personalisation-group-definition .umb-property { - margin-bottom: 16px; -} -.personalisation-group-definition label { - margin-top: 6px; -} -.personalisation-group-definition .help-inline { - display: block; - font-size: 80%; -} -.personalisation-group-definition .umb-property .controls { - margin-left: 0; -} - diff --git a/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisaton-group-definition.controller.js b/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisaton-group-definition.controller.js deleted file mode 100644 index d2c78d2..0000000 --- a/PersonalisationGroups/App_Plugins/PersonalisationGroups/personalisaton-group-definition.controller.js +++ /dev/null @@ -1,133 +0,0 @@ -angular.module("umbraco") - .controller("PersonalisationGroups.PersonalisationGroupDefinitionController", - function ($scope, $http, $injector, editorService) { - - var translators = []; - var editingNew = false; - - function convertToPascalCase(s) { - return s.charAt(0).toUpperCase() + s.substr(1); - } - - function loadTranslators() { - for (var i = 0; i < $scope.availableCriteria.length; i++) { - translators.push($injector.get("PersonalisationGroups." + convertToPascalCase($scope.availableCriteria[i].alias) + "TranslatorService")); - } - } - - function initAvailableCriteriaList() { - $scope.availableCriteria = []; - $http.get("/App_Plugins/PersonalisationGroups/Criteria") - .then(function (result) { - $scope.availableCriteria = result.data; - if (result.data.length > 0) { - $scope.selectedCriteria = result.data[0]; - } - - loadTranslators(); - }); - }; - - function getCriteriaByAlias(alias) { - for (var i = 0; i < $scope.availableCriteria.length; i++) { - if ($scope.availableCriteria[i].alias === alias) { - return $scope.availableCriteria[i]; - } - } - - return null; - }; - - function getCriteriaIndexByAlias(alias) { - var index = 0; - for (var i = 0; i < $scope.availableCriteria.length; i++) { - if ($scope.availableCriteria[i].alias === alias) { - return index; - } - - index++; - } - - return -1; - }; - - function convertAliasToFolderName(alias) { - return alias.charAt(0).toUpperCase() + alias.slice(1); - } - - if (!$scope.model.value) { - $scope.model.value = { match: "All", duration: "Page", score: 50, details: [] }; - } - - if (!$scope.model.value.duration) { - $scope.model.value.duration = "Page"; - } - - if (!$scope.model.value.score) { - $scope.model.value.score = 50; - } - - $scope.selectedCriteria = null; - - initAvailableCriteriaList(); - - $scope.addCriteria = function () { - var detail = { alias: $scope.selectedCriteria.alias, definition: "" }; - $scope.model.value.details.push(detail); - $scope.editDefinitionDetail(detail); - editingNew = true; - }; - - $scope.editDefinitionDetail = function (definitionDetail) { - editingNew = false; - - var clientAssetsFolder = getCriteriaByAlias(definitionDetail.alias).clientAssetsFolder; - if (!clientAssetsFolder) { - clientAssetsFolder = "PersonalisationGroups/Criteria"; - } - - var templateUrl = "/App_Plugins/" + clientAssetsFolder + "/" + convertAliasToFolderName(definitionDetail.alias) + "/definition.editor.html"; - - editorService.open( - { - title: "Edit definition detail", - view: templateUrl, - size: "small", - definition: definitionDetail.definition, - submit: function (data) { - definitionDetail.definition = data; - editorService.close(); - }, - close: function () { - if (editingNew) { - // If we've cancelled a new one, we don't want an empty record - $scope.model.value.details.pop(); - } - - editorService.close(); - } - }); - }; - - $scope.delete = function (index) { - $scope.model.value.details.splice(index, 1); - }; - - $scope.getCriteriaName = function (alias) { - var criteria = getCriteriaByAlias(alias); - if (criteria) { - return criteria.name; - } - - return ""; - }; - - $scope.getDefinitionTranslation = function (definitionDetail) { - var translator = translators[getCriteriaIndexByAlias(definitionDetail.alias)]; - if (translator) { - return translator.translate(definitionDetail.definition); - } - - return ""; - }; - }); diff --git a/PersonalisationGroups/Attributes/CriteriaResourceAssemblyAttribute.cs b/PersonalisationGroups/Attributes/CriteriaResourceAssemblyAttribute.cs new file mode 100644 index 0000000..ab21695 --- /dev/null +++ b/PersonalisationGroups/Attributes/CriteriaResourceAssemblyAttribute.cs @@ -0,0 +1,9 @@ +namespace Our.Umbraco.PersonalisationGroups.Attributes; + +using System; + +[AttributeUsage(AttributeTargets.Class)] +public class CriteriaResourceAssemblyAttribute : Attribute +{ + public required string AssemblyName { get; set; } +} diff --git a/PersonalisationGroups/Client/.eslintrc.json b/PersonalisationGroups/Client/.eslintrc.json new file mode 100644 index 0000000..c0da9b4 --- /dev/null +++ b/PersonalisationGroups/Client/.eslintrc.json @@ -0,0 +1,87 @@ +{ + "ignorePatterns": ["vite.*.ts", "src/generated/**", "devops/**"], + "root": true, + "plugins": ["eslint-plugin-local-rules"], + "parserOptions": { + "ecmaVersion": "latest" + }, + "env": { + "es6": true + }, + "overrides": [ + { + "files": ["**/*.ts"], + "extends": [ + "eslint:recommended", + "plugin:import/recommended", + "plugin:import/typescript", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:wc/recommended", + "plugin:lit/recommended", + "plugin:lit-a11y/recommended", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./tsconfig.json", + "tsconfigRootDir": "./", + "ecmaVersion": "latest", + "sourceType": "module" + }, + "env": { + "browser": true, + "es2021": true + }, + "rules": { + "no-var": "error", + "import/no-unresolved": "off", + "import/order": "warn", + "import/no-duplicates": ["warn", {"prefer-inline": true}], + "local-rules/bad-type-import": "error", + "local-rules/no-direct-api-import": "warn", + "local-rules/prefer-import-aliases": "error", + "local-rules/enforce-element-suffix-on-element-class-name": "error", + "local-rules/prefer-static-styles-last": "warn", + "local-rules/ensure-relative-import-use-js-extension": "error", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": "warn", + "@typescript-eslint/consistent-type-exports": "error", + "@typescript-eslint/consistent-type-imports": "error" + }, + "settings": { + "import/parsers": { + "@typescript-eslint/parser": [".ts"] + }, + "import/resolver": { + "typescript": { + "alwaysTryTypes": true, + "project": "./tsconfig.json" + } + } + } + }, + { + "files": ["**/*.js"], + "extends": ["eslint:recommended", "plugin:import/recommended", "prettier"], + "env": { + "node": true, + "browser": true, + "es6": true + }, + "parserOptions": { + "sourceType": "module", + "ecmaVersion": "latest" + }, + "settings": { + "import/resolver": { + "node": { + "extensions": [".js"], + "moduleDirectory": ["node_modules"] + } + } + } + } + ] +} diff --git a/PersonalisationGroups/Client/.gitignore b/PersonalisationGroups/Client/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/PersonalisationGroups/Client/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/PersonalisationGroups/Client/config.outputPath.js b/PersonalisationGroups/Client/config.outputPath.js new file mode 100644 index 0000000..ebdac79 --- /dev/null +++ b/PersonalisationGroups/Client/config.outputPath.js @@ -0,0 +1 @@ +export const outputPath = 'Debug' !== 'Release' ? '../wwwroot' : '../obj/Debug/net8.0/clientassets' diff --git a/PersonalisationGroups/Client/eslint-local-rules.cjs b/PersonalisationGroups/Client/eslint-local-rules.cjs new file mode 100644 index 0000000..af6f202 --- /dev/null +++ b/PersonalisationGroups/Client/eslint-local-rules.cjs @@ -0,0 +1,19 @@ +"use strict"; + +const badTypeImportRule = require("./devops/eslint/rules/bad-type-import.cjs"); +const enforceElementSuffixOnElementClassNameRule = require("./devops/eslint/rules/enforce-element-suffix-on-element-class-name.cjs"); +const ensureRelativeImportUseJsExtensionRule = require("./devops/eslint/rules/ensure-relative-import-use-js-extension.cjs"); +const noDirectApiImportRule = require("./devops/eslint/rules/no-direct-api-import.cjs"); +const preferImportAliasesRule = require("./devops/eslint/rules/prefer-import-aliases.cjs"); +const preferStaticStylesLastRule = require("./devops/eslint/rules/prefer-static-styles-last.cjs"); + +module.exports = { + "bad-type-import": badTypeImportRule, + "enforce-element-suffix-on-element-class-name": + enforceElementSuffixOnElementClassNameRule, + "ensure-relative-import-use-js-extension": + ensureRelativeImportUseJsExtensionRule, + "no-direct-api-import": noDirectApiImportRule, + "prefer-import-aliases": preferImportAliasesRule, + "prefer-static-styles-last": preferStaticStylesLastRule, +}; diff --git a/PersonalisationGroups/Client/generated/core/ApiError.ts b/PersonalisationGroups/Client/generated/core/ApiError.ts new file mode 100644 index 0000000..36675d2 --- /dev/null +++ b/PersonalisationGroups/Client/generated/core/ApiError.ts @@ -0,0 +1,21 @@ +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; + +export class ApiError extends Error { + public readonly url: string; + public readonly status: number; + public readonly statusText: string; + public readonly body: unknown; + public readonly request: ApiRequestOptions; + + constructor(request: ApiRequestOptions, response: ApiResult, message: string) { + super(message); + + this.name = 'ApiError'; + this.url = response.url; + this.status = response.status; + this.statusText = response.statusText; + this.body = response.body; + this.request = request; + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/generated/core/ApiRequestOptions.ts b/PersonalisationGroups/Client/generated/core/ApiRequestOptions.ts new file mode 100644 index 0000000..f25a891 --- /dev/null +++ b/PersonalisationGroups/Client/generated/core/ApiRequestOptions.ts @@ -0,0 +1,13 @@ +export type ApiRequestOptions = { + readonly method: 'GET' | 'PUT' | 'POST' | 'DELETE' | 'OPTIONS' | 'HEAD' | 'PATCH'; + readonly url: string; + readonly path?: Record; + readonly cookies?: Record; + readonly headers?: Record; + readonly query?: Record; + readonly formData?: Record; + readonly body?: any; + readonly mediaType?: string; + readonly responseHeader?: string; + readonly errors?: Record; +}; \ No newline at end of file diff --git a/PersonalisationGroups/Client/generated/core/ApiResult.ts b/PersonalisationGroups/Client/generated/core/ApiResult.ts new file mode 100644 index 0000000..4c58e39 --- /dev/null +++ b/PersonalisationGroups/Client/generated/core/ApiResult.ts @@ -0,0 +1,7 @@ +export type ApiResult = { + readonly body: TData; + readonly ok: boolean; + readonly status: number; + readonly statusText: string; + readonly url: string; +}; \ No newline at end of file diff --git a/PersonalisationGroups/Client/generated/core/CancelablePromise.ts b/PersonalisationGroups/Client/generated/core/CancelablePromise.ts new file mode 100644 index 0000000..ccc082e --- /dev/null +++ b/PersonalisationGroups/Client/generated/core/CancelablePromise.ts @@ -0,0 +1,126 @@ +export class CancelError extends Error { + constructor(message: string) { + super(message); + this.name = 'CancelError'; + } + + public get isCancelled(): boolean { + return true; + } +} + +export interface OnCancel { + readonly isResolved: boolean; + readonly isRejected: boolean; + readonly isCancelled: boolean; + + (cancelHandler: () => void): void; +} + +export class CancelablePromise implements Promise { + private _isResolved: boolean; + private _isRejected: boolean; + private _isCancelled: boolean; + readonly cancelHandlers: (() => void)[]; + readonly promise: Promise; + private _resolve?: (value: T | PromiseLike) => void; + private _reject?: (reason?: unknown) => void; + + constructor( + executor: ( + resolve: (value: T | PromiseLike) => void, + reject: (reason?: unknown) => void, + onCancel: OnCancel + ) => void + ) { + this._isResolved = false; + this._isRejected = false; + this._isCancelled = false; + this.cancelHandlers = []; + this.promise = new Promise((resolve, reject) => { + this._resolve = resolve; + this._reject = reject; + + const onResolve = (value: T | PromiseLike): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isResolved = true; + if (this._resolve) this._resolve(value); + }; + + const onReject = (reason?: unknown): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isRejected = true; + if (this._reject) this._reject(reason); + }; + + const onCancel = (cancelHandler: () => void): void => { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this.cancelHandlers.push(cancelHandler); + }; + + Object.defineProperty(onCancel, 'isResolved', { + get: (): boolean => this._isResolved, + }); + + Object.defineProperty(onCancel, 'isRejected', { + get: (): boolean => this._isRejected, + }); + + Object.defineProperty(onCancel, 'isCancelled', { + get: (): boolean => this._isCancelled, + }); + + return executor(onResolve, onReject, onCancel as OnCancel); + }); + } + + get [Symbol.toStringTag]() { + return "Cancellable Promise"; + } + + public then( + onFulfilled?: ((value: T) => TResult1 | PromiseLike) | null, + onRejected?: ((reason: unknown) => TResult2 | PromiseLike) | null + ): Promise { + return this.promise.then(onFulfilled, onRejected); + } + + public catch( + onRejected?: ((reason: unknown) => TResult | PromiseLike) | null + ): Promise { + return this.promise.catch(onRejected); + } + + public finally(onFinally?: (() => void) | null): Promise { + return this.promise.finally(onFinally); + } + + public cancel(): void { + if (this._isResolved || this._isRejected || this._isCancelled) { + return; + } + this._isCancelled = true; + if (this.cancelHandlers.length) { + try { + for (const cancelHandler of this.cancelHandlers) { + cancelHandler(); + } + } catch (error) { + console.warn('Cancellation threw an error', error); + return; + } + } + this.cancelHandlers.length = 0; + if (this._reject) this._reject(new CancelError('Request aborted')); + } + + public get isCancelled(): boolean { + return this._isCancelled; + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/generated/core/OpenAPI.ts b/PersonalisationGroups/Client/generated/core/OpenAPI.ts new file mode 100644 index 0000000..b13d1d3 --- /dev/null +++ b/PersonalisationGroups/Client/generated/core/OpenAPI.ts @@ -0,0 +1,56 @@ +import type { ApiRequestOptions } from './ApiRequestOptions'; + +type Headers = Record; +type Middleware = (value: T) => T | Promise; +type Resolver = (options: ApiRequestOptions) => Promise; + +export class Interceptors { + _fns: Middleware[]; + + constructor() { + this._fns = []; + } + + eject(fn: Middleware): void { + const index = this._fns.indexOf(fn); + if (index !== -1) { + this._fns = [...this._fns.slice(0, index), ...this._fns.slice(index + 1)]; + } + } + + use(fn: Middleware): void { + this._fns = [...this._fns, fn]; + } +} + +export type OpenAPIConfig = { + BASE: string; + CREDENTIALS: 'include' | 'omit' | 'same-origin'; + ENCODE_PATH?: ((path: string) => string) | undefined; + HEADERS?: Headers | Resolver | undefined; + PASSWORD?: string | Resolver | undefined; + TOKEN?: string | Resolver | undefined; + USERNAME?: string | Resolver | undefined; + VERSION: string; + WITH_CREDENTIALS: boolean; + interceptors: { + request: Interceptors; + response: Interceptors; + }; +}; + +export const OpenAPI: OpenAPIConfig = { + BASE: '', + CREDENTIALS: 'include', + ENCODE_PATH: undefined, + HEADERS: undefined, + PASSWORD: undefined, + TOKEN: undefined, + USERNAME: undefined, + VERSION: 'Latest', + WITH_CREDENTIALS: false, + interceptors: { + request: new Interceptors(), + response: new Interceptors(), + }, +}; \ No newline at end of file diff --git a/PersonalisationGroups/Client/generated/core/request.ts b/PersonalisationGroups/Client/generated/core/request.ts new file mode 100644 index 0000000..79d04d0 --- /dev/null +++ b/PersonalisationGroups/Client/generated/core/request.ts @@ -0,0 +1,341 @@ +import { ApiError } from './ApiError'; +import type { ApiRequestOptions } from './ApiRequestOptions'; +import type { ApiResult } from './ApiResult'; +import { CancelablePromise } from './CancelablePromise'; +import type { OnCancel } from './CancelablePromise'; +import type { OpenAPIConfig } from './OpenAPI'; + +export const isString = (value: unknown): value is string => { + return typeof value === 'string'; +}; + +export const isStringWithValue = (value: unknown): value is string => { + return isString(value) && value !== ''; +}; + +export const isBlob = (value: any): value is Blob => { + return value instanceof Blob; +}; + +export const isFormData = (value: unknown): value is FormData => { + return value instanceof FormData; +}; + +export const base64 = (str: string): string => { + try { + return btoa(str); + } catch (err) { + // @ts-ignore + return Buffer.from(str).toString('base64'); + } +}; + +export const getQueryString = (params: Record): string => { + const qs: string[] = []; + + const append = (key: string, value: unknown) => { + qs.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`); + }; + + const encodePair = (key: string, value: unknown) => { + if (value === undefined || value === null) { + return; + } + + if (value instanceof Date) { + append(key, value.toISOString()); + } else if (Array.isArray(value)) { + value.forEach(v => encodePair(key, v)); + } else if (typeof value === 'object') { + Object.entries(value).forEach(([k, v]) => encodePair(`${key}[${k}]`, v)); + } else { + append(key, value); + } + }; + + Object.entries(params).forEach(([key, value]) => encodePair(key, value)); + + return qs.length ? `?${qs.join('&')}` : ''; +}; + +const getUrl = (config: OpenAPIConfig, options: ApiRequestOptions): string => { + const encoder = config.ENCODE_PATH || encodeURI; + + const path = options.url + .replace('{api-version}', config.VERSION) + .replace(/{(.*?)}/g, (substring: string, group: string) => { + if (options.path?.hasOwnProperty(group)) { + return encoder(String(options.path[group])); + } + return substring; + }); + + const url = config.BASE + path; + return options.query ? url + getQueryString(options.query) : url; +}; + +export const getFormData = (options: ApiRequestOptions): FormData | undefined => { + if (options.formData) { + const formData = new FormData(); + + const process = (key: string, value: unknown) => { + if (isString(value) || isBlob(value)) { + formData.append(key, value); + } else { + formData.append(key, JSON.stringify(value)); + } + }; + + Object.entries(options.formData) + .filter(([, value]) => value !== undefined && value !== null) + .forEach(([key, value]) => { + if (Array.isArray(value)) { + value.forEach(v => process(key, v)); + } else { + process(key, value); + } + }); + + return formData; + } + return undefined; +}; + +type Resolver = (options: ApiRequestOptions) => Promise; + +export const resolve = async (options: ApiRequestOptions, resolver?: T | Resolver): Promise => { + if (typeof resolver === 'function') { + return (resolver as Resolver)(options); + } + return resolver; +}; + +export const getHeaders = async (config: OpenAPIConfig, options: ApiRequestOptions): Promise => { + const [token, username, password, additionalHeaders] = await Promise.all([ + resolve(options, config.TOKEN), + resolve(options, config.USERNAME), + resolve(options, config.PASSWORD), + resolve(options, config.HEADERS), + ]); + + const headers = Object.entries({ + Accept: 'application/json', + ...additionalHeaders, + ...options.headers, + }) + .filter(([, value]) => value !== undefined && value !== null) + .reduce((headers, [key, value]) => ({ + ...headers, + [key]: String(value), + }), {} as Record); + + if (isStringWithValue(token)) { + headers['Authorization'] = `Bearer ${token}`; + } + + if (isStringWithValue(username) && isStringWithValue(password)) { + const credentials = base64(`${username}:${password}`); + headers['Authorization'] = `Basic ${credentials}`; + } + + if (options.body !== undefined) { + if (options.mediaType) { + headers['Content-Type'] = options.mediaType; + } else if (isBlob(options.body)) { + headers['Content-Type'] = options.body.type || 'application/octet-stream'; + } else if (isString(options.body)) { + headers['Content-Type'] = 'text/plain'; + } else if (!isFormData(options.body)) { + headers['Content-Type'] = 'application/json'; + } + } + + return new Headers(headers); +}; + +export const getRequestBody = (options: ApiRequestOptions): unknown => { + if (options.body !== undefined) { + if (options.mediaType?.includes('application/json') || options.mediaType?.includes('+json')) { + return JSON.stringify(options.body); + } else if (isString(options.body) || isBlob(options.body) || isFormData(options.body)) { + return options.body; + } else { + return JSON.stringify(options.body); + } + } + return undefined; +}; + +export const sendRequest = async ( + config: OpenAPIConfig, + options: ApiRequestOptions, + url: string, + body: any, + formData: FormData | undefined, + headers: Headers, + onCancel: OnCancel +): Promise => { + const controller = new AbortController(); + + let request: RequestInit = { + headers, + body: body ?? formData, + method: options.method, + signal: controller.signal, + }; + + if (config.WITH_CREDENTIALS) { + request.credentials = config.CREDENTIALS; + } + + for (const fn of config.interceptors.request._fns) { + request = await fn(request); + } + + onCancel(() => controller.abort()); + + return await fetch(url, request); +}; + +export const getResponseHeader = (response: Response, responseHeader?: string): string | undefined => { + if (responseHeader) { + const content = response.headers.get(responseHeader); + if (isString(content)) { + return content; + } + } + return undefined; +}; + +export const getResponseBody = async (response: Response): Promise => { + if (response.status !== 204) { + try { + const contentType = response.headers.get('Content-Type'); + if (contentType) { + const binaryTypes = ['application/octet-stream', 'application/pdf', 'application/zip', 'audio/', 'image/', 'video/']; + if (contentType.includes('application/json') || contentType.includes('+json')) { + return await response.json(); + } else if (binaryTypes.some(type => contentType.includes(type))) { + return await response.blob(); + } else if (contentType.includes('multipart/form-data')) { + return await response.formData(); + } else if (contentType.includes('text/')) { + return await response.text(); + } + } + } catch (error) { + console.error(error); + } + } + return undefined; +}; + +export const catchErrorCodes = (options: ApiRequestOptions, result: ApiResult): void => { + const errors: Record = { + 400: 'Bad Request', + 401: 'Unauthorized', + 402: 'Payment Required', + 403: 'Forbidden', + 404: 'Not Found', + 405: 'Method Not Allowed', + 406: 'Not Acceptable', + 407: 'Proxy Authentication Required', + 408: 'Request Timeout', + 409: 'Conflict', + 410: 'Gone', + 411: 'Length Required', + 412: 'Precondition Failed', + 413: 'Payload Too Large', + 414: 'URI Too Long', + 415: 'Unsupported Media Type', + 416: 'Range Not Satisfiable', + 417: 'Expectation Failed', + 418: 'Im a teapot', + 421: 'Misdirected Request', + 422: 'Unprocessable Content', + 423: 'Locked', + 424: 'Failed Dependency', + 425: 'Too Early', + 426: 'Upgrade Required', + 428: 'Precondition Required', + 429: 'Too Many Requests', + 431: 'Request Header Fields Too Large', + 451: 'Unavailable For Legal Reasons', + 500: 'Internal Server Error', + 501: 'Not Implemented', + 502: 'Bad Gateway', + 503: 'Service Unavailable', + 504: 'Gateway Timeout', + 505: 'HTTP Version Not Supported', + 506: 'Variant Also Negotiates', + 507: 'Insufficient Storage', + 508: 'Loop Detected', + 510: 'Not Extended', + 511: 'Network Authentication Required', + ...options.errors, + } + + const error = errors[result.status]; + if (error) { + throw new ApiError(options, result, error); + } + + if (!result.ok) { + const errorStatus = result.status ?? 'unknown'; + const errorStatusText = result.statusText ?? 'unknown'; + const errorBody = (() => { + try { + return JSON.stringify(result.body, null, 2); + } catch (e) { + return undefined; + } + })(); + + throw new ApiError(options, result, + `Generic Error: status: ${errorStatus}; status text: ${errorStatusText}; body: ${errorBody}` + ); + } +}; + +/** + * Request method + * @param config The OpenAPI configuration object + * @param options The request options from the service + * @returns CancelablePromise + * @throws ApiError + */ +export const request = (config: OpenAPIConfig, options: ApiRequestOptions): CancelablePromise => { + return new CancelablePromise(async (resolve, reject, onCancel) => { + try { + const url = getUrl(config, options); + const formData = getFormData(options); + const body = getRequestBody(options); + const headers = await getHeaders(config, options); + + if (!onCancel.isCancelled) { + let response = await sendRequest(config, options, url, body, formData, headers, onCancel); + + for (const fn of config.interceptors.response._fns) { + response = await fn(response); + } + + const responseBody = await getResponseBody(response); + const responseHeader = getResponseHeader(response, options.responseHeader); + + const result: ApiResult = { + url, + ok: response.ok, + status: response.status, + statusText: response.statusText, + body: responseHeader ?? responseBody, + }; + + catchErrorCodes(options, result); + + resolve(result.body); + } + } catch (error) { + reject(error); + } + }); +}; \ No newline at end of file diff --git a/PersonalisationGroups/Client/generated/index.ts b/PersonalisationGroups/Client/generated/index.ts new file mode 100644 index 0000000..3e1f0b3 --- /dev/null +++ b/PersonalisationGroups/Client/generated/index.ts @@ -0,0 +1,6 @@ +// This file is auto-generated by @hey-api/openapi-ts +export { ApiError } from './core/ApiError'; +export { CancelablePromise, CancelError } from './core/CancelablePromise'; +export { OpenAPI, type OpenAPIConfig } from './core/OpenAPI'; +export * from './services.gen'; +export * from './types.gen'; \ No newline at end of file diff --git a/PersonalisationGroups/Client/generated/services.gen.ts b/PersonalisationGroups/Client/generated/services.gen.ts new file mode 100644 index 0000000..509465b --- /dev/null +++ b/PersonalisationGroups/Client/generated/services.gen.ts @@ -0,0 +1,123 @@ +// This file is auto-generated by @hey-api/openapi-ts + +import type { CancelablePromise } from './core/CancelablePromise'; +import { OpenAPI } from './core/OpenAPI'; +import { request as __request } from './core/request'; +import type { GetCriteriaCollectionResponse, GetContinentCollectionResponse, GetCountryCollectionData, GetCountryCollectionResponse, GetRegionCollectionData, GetRegionCollectionResponse, GetMemberGroupCollectionResponse, GetMemberProfileFieldCollectionResponse, GetMemberTypeCollectionResponse } from './types.gen'; + +export class CriteriaService { + /** + * @returns unknown OK + * @throws ApiError + */ + public static getCriteriaCollection(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/personalisation-groups/management/api/v1/criteria', + errors: { + 401: 'The resource is protected and requires an authentication token' + } + }); + } + +} + +export class GeoLocationService { + /** + * @returns unknown OK + * @throws ApiError + */ + public static getContinentCollection(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/personalisation-groups/management/api/v1/continent', + errors: { + 401: 'The resource is protected and requires an authentication token' + } + }); + } + + /** + * @param data The data for the request. + * @param data.withRegionsOnly + * @returns unknown OK + * @throws ApiError + */ + public static getCountryCollection(data: GetCountryCollectionData = {}): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/personalisation-groups/management/api/v1/country', + query: { + withRegionsOnly: data.withRegionsOnly + }, + errors: { + 401: 'The resource is protected and requires an authentication token' + } + }); + } + + /** + * @param data The data for the request. + * @param data.countryCode + * @returns unknown OK + * @throws ApiError + */ + public static getRegionCollection(data: GetRegionCollectionData): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/personalisation-groups/management/api/v1/country/{countryCode}/region', + path: { + countryCode: data.countryCode + }, + errors: { + 401: 'The resource is protected and requires an authentication token' + } + }); + } + +} + +export class MemberService { + /** + * @returns unknown OK + * @throws ApiError + */ + public static getMemberGroupCollection(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/personalisation-groups/management/api/v1/member-group', + errors: { + 401: 'The resource is protected and requires an authentication token' + } + }); + } + + /** + * @returns unknown OK + * @throws ApiError + */ + public static getMemberProfileFieldCollection(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/personalisation-groups/management/api/v1/member-profile-field', + errors: { + 401: 'The resource is protected and requires an authentication token' + } + }); + } + + /** + * @returns unknown OK + * @throws ApiError + */ + public static getMemberTypeCollection(): CancelablePromise { + return __request(OpenAPI, { + method: 'GET', + url: '/umbraco/personalisation-groups/management/api/v1/member-type', + errors: { + 401: 'The resource is protected and requires an authentication token' + } + }); + } + +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/generated/types.gen.ts b/PersonalisationGroups/Client/generated/types.gen.ts new file mode 100644 index 0000000..34c56fc --- /dev/null +++ b/PersonalisationGroups/Client/generated/types.gen.ts @@ -0,0 +1,161 @@ +// This file is auto-generated by @hey-api/openapi-ts + +export type ContinentDto = { + code: string; + name: string; +}; + +export type CountryDto = { + code: string; + name: string; +}; + +export type CriteriaDto = { + name: string; + alias: string; + description: string; +}; + +export type MemberGroupDto = { + name: string; +}; + +export type MemberProfileFieldDto = { + alias: string; + name: string; +}; + +export type MemberTypeDto = { + alias: string; + name: string; +}; + +export type RegionDto = { + code: string; + name: string; +}; + +export type GetCriteriaCollectionResponse = Array<(CriteriaDto)>; + +export type GetContinentCollectionResponse = Array<(ContinentDto)>; + +export type GetCountryCollectionData = { + withRegionsOnly?: boolean; +}; + +export type GetCountryCollectionResponse = Array<(CountryDto)>; + +export type GetRegionCollectionData = { + countryCode: string; +}; + +export type GetRegionCollectionResponse = Array<(RegionDto)>; + +export type GetMemberGroupCollectionResponse = Array<(MemberGroupDto)>; + +export type GetMemberProfileFieldCollectionResponse = Array<(MemberProfileFieldDto)>; + +export type GetMemberTypeCollectionResponse = Array<(MemberTypeDto)>; + +export type $OpenApiTs = { + '/umbraco/personalisation-groups/management/api/v1/criteria': { + get: { + res: { + /** + * OK + */ + 200: Array<(CriteriaDto)>; + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; + }; + }; + }; + '/umbraco/personalisation-groups/management/api/v1/continent': { + get: { + res: { + /** + * OK + */ + 200: Array<(ContinentDto)>; + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; + }; + }; + }; + '/umbraco/personalisation-groups/management/api/v1/country': { + get: { + req: GetCountryCollectionData; + res: { + /** + * OK + */ + 200: Array<(CountryDto)>; + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; + }; + }; + }; + '/umbraco/personalisation-groups/management/api/v1/country/{countryCode}/region': { + get: { + req: GetRegionCollectionData; + res: { + /** + * OK + */ + 200: Array<(RegionDto)>; + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; + }; + }; + }; + '/umbraco/personalisation-groups/management/api/v1/member-group': { + get: { + res: { + /** + * OK + */ + 200: Array<(MemberGroupDto)>; + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; + }; + }; + }; + '/umbraco/personalisation-groups/management/api/v1/member-profile-field': { + get: { + res: { + /** + * OK + */ + 200: Array<(MemberProfileFieldDto)>; + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; + }; + }; + }; + '/umbraco/personalisation-groups/management/api/v1/member-type': { + get: { + res: { + /** + * OK + */ + 200: Array<(MemberTypeDto)>; + /** + * The resource is protected and requires an authentication token + */ + 401: unknown; + }; + }; + }; +}; \ No newline at end of file diff --git a/PersonalisationGroups/Client/openapi-ts.config.ts b/PersonalisationGroups/Client/openapi-ts.config.ts new file mode 100644 index 0000000..2de839d --- /dev/null +++ b/PersonalisationGroups/Client/openapi-ts.config.ts @@ -0,0 +1,14 @@ +export default { + input: 'http://localhost:42192/umbraco/swagger/personalisation-groups-management/swagger.json', + output: { + lint: 'eslint', + path: 'generated', + }, + schemas: false, + services: { + asClass: true + }, + types: { + enums: 'typescript', + } + } diff --git a/PersonalisationGroups/Client/package-lock.json b/PersonalisationGroups/Client/package-lock.json new file mode 100644 index 0000000..b96b5ae --- /dev/null +++ b/PersonalisationGroups/Client/package-lock.json @@ -0,0 +1,6001 @@ +{ + "name": "umbraco-authorizedservices", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "umbraco-authorizedservices", + "version": "0.0.0", + "devDependencies": { + "@hey-api/openapi-ts": "^0.46.3", + "@typescript-eslint/eslint-plugin": "^7.13.0", + "@typescript-eslint/parser": "^7.13.0", + "@umbraco-cms/backoffice": "^14.0.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-lit": "^1.11.0", + "eslint-plugin-lit-a11y": "^4.1.1", + "eslint-plugin-local-rules": "^2.0.1", + "eslint-plugin-wc": "^2.0.4", + "typescript": "^5.2.2", + "vite": "^5.2.0", + "vite-tsconfig-paths": "^4.3.1" + }, + "engines": { + "node": ">=20.9 <21", + "npm": ">=10.1 < 11" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "11.6.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.6.2.tgz", + "integrity": "sha512-ENUdLLT04aDbbHCRwfKf8gR67AhV0CdFrOAtk+FcakBAgaq6ds3HLK9X0BCyiFUz8pK9uP+k6YZyJaGG7Mt7vQ==", + "dev": true, + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.1.tgz", + "integrity": "sha512-Zm2NGpWELsQAD1xsJzGQpYfvICSsFkEpU0jxBjfdC6uNEWXcHnfs9hScFWtXVDVl+rBQJGrl4g1vcKIejpH9dA==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@hey-api/openapi-ts": { + "version": "0.46.3", + "resolved": "https://registry.npmjs.org/@hey-api/openapi-ts/-/openapi-ts-0.46.3.tgz", + "integrity": "sha512-2C0tmzUq6m9518uDMHBooqiS7wvWy9UD6SepudWU+9fB6lZvC+yxbK/viaMfwvCv4l1hKuBZZZQNP0xrNcyRsQ==", + "dev": true, + "dependencies": { + "@apidevtools/json-schema-ref-parser": "11.6.2", + "c12": "1.10.0", + "camelcase": "8.0.0", + "commander": "12.1.0", + "handlebars": "4.7.8" + }, + "bin": { + "openapi-ts": "bin/index.cjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "typescript": "^5.x" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "dev": true + }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz", + "integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g==", + "dev": true, + "peer": true + }, + "node_modules/@lit/reactive-element": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz", + "integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==", + "dev": true, + "peer": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@thepassle/axobject-query": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@thepassle/axobject-query/-/axobject-query-4.0.0.tgz", + "integrity": "sha512-/LHo+2jOdxs2WtbGocr3/lDSzsnjgCV6DSoBf4Y1Q0D24Hu67NPWuneoJimfHu5auqqSWi1fAvtln2013VxVqg==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/@types/diff": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@types/diff/-/diff-5.2.1.tgz", + "integrity": "sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==", + "dev": true, + "peer": true + }, + "node_modules/@types/dompurify": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz", + "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/trusted-types": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "20.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", + "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/parse5": { + "version": "2.2.34", + "resolved": "https://registry.npmjs.org/@types/parse5/-/parse5-2.2.34.tgz", + "integrity": "sha512-p3qOvaRsRpFyEmaS36RtLzpdxZZnmxGuT1GMgzkTtTJVFuEw7KFjGK83MFODpJExgX1bEzy9r0NYjMC3IMfi7w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "peer": true + }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true, + "peer": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.14.1.tgz", + "integrity": "sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/type-utils": "7.14.1", + "@typescript-eslint/utils": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.14.1.tgz", + "integrity": "sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz", + "integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.14.1.tgz", + "integrity": "sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/utils": "7.14.1", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz", + "integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz", + "integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.14.1.tgz", + "integrity": "sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz", + "integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "7.14.1", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@umbraco-cms/backoffice": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@umbraco-cms/backoffice/-/backoffice-14.0.0.tgz", + "integrity": "sha512-PpJXHBeqDEEKTf4L/K7d8Ua9E2G9VuXHL76lBmBrf+1Sa7iWVuqf5BCvSa0wy1hKLrU1ytLsmByvVHEcix4XOw==", + "dev": true, + "engines": { + "node": ">=20.9 <21", + "npm": ">=10.1 < 11" + }, + "peerDependencies": { + "@types/diff": "^5.2.1", + "@types/dompurify": "^3.0.5", + "@types/uuid": "^9.0.8", + "@umbraco-ui/uui": "1.8.1", + "@umbraco-ui/uui-css": "1.8.0", + "base64-js": "^1.5.1", + "diff": "^5.2.0", + "dompurify": "^3.1.4", + "element-internals-polyfill": "^1.3.11", + "lit": "^3.1.3", + "marked": "^12.0.2", + "monaco-editor": "^0.48.0", + "rxjs": "^7.8.1", + "tinymce": "^6.8.3", + "tinymce-i18n": "^24.5.8", + "uuid": "^9.0.1" + } + }, + "node_modules/@umbraco-ui/uui": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui/-/uui-1.8.1.tgz", + "integrity": "sha512-KWKtuSQdxeCbGH2gezumuFysf+33q2TZy4h85zCABFvHgOeeR1EB7/S2jUNGoe2IOqYLktYl0GdQszfmyFN1dg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-action-bar": "1.8.0", + "@umbraco-ui/uui-avatar": "1.8.0", + "@umbraco-ui/uui-avatar-group": "1.8.0", + "@umbraco-ui/uui-badge": "1.8.0", + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-boolean-input": "1.8.0", + "@umbraco-ui/uui-box": "1.8.0", + "@umbraco-ui/uui-breadcrumbs": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-button-group": "1.8.0", + "@umbraco-ui/uui-button-inline-create": "1.8.0", + "@umbraco-ui/uui-card": "1.8.0", + "@umbraco-ui/uui-card-block-type": "1.8.0", + "@umbraco-ui/uui-card-content-node": "1.8.0", + "@umbraco-ui/uui-card-media": "1.8.0", + "@umbraco-ui/uui-card-user": "1.8.0", + "@umbraco-ui/uui-caret": "1.8.0", + "@umbraco-ui/uui-checkbox": "1.8.0", + "@umbraco-ui/uui-color-area": "1.8.0", + "@umbraco-ui/uui-color-picker": "1.8.0", + "@umbraco-ui/uui-color-slider": "1.8.0", + "@umbraco-ui/uui-color-swatch": "1.8.0", + "@umbraco-ui/uui-color-swatches": "1.8.0", + "@umbraco-ui/uui-combobox": "1.8.0", + "@umbraco-ui/uui-combobox-list": "1.8.0", + "@umbraco-ui/uui-css": "1.8.0", + "@umbraco-ui/uui-dialog": "1.8.0", + "@umbraco-ui/uui-dialog-layout": "1.8.0", + "@umbraco-ui/uui-file-dropzone": "1.8.0", + "@umbraco-ui/uui-file-preview": "1.8.0", + "@umbraco-ui/uui-form": "1.8.0", + "@umbraco-ui/uui-form-layout-item": "1.8.1", + "@umbraco-ui/uui-form-validation-message": "1.8.1", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-icon-registry": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0", + "@umbraco-ui/uui-input": "1.8.0", + "@umbraco-ui/uui-input-file": "1.8.0", + "@umbraco-ui/uui-input-lock": "1.8.0", + "@umbraco-ui/uui-input-password": "1.8.0", + "@umbraco-ui/uui-keyboard-shortcut": "1.8.0", + "@umbraco-ui/uui-label": "1.8.0", + "@umbraco-ui/uui-loader": "1.8.0", + "@umbraco-ui/uui-loader-bar": "1.8.0", + "@umbraco-ui/uui-loader-circle": "1.8.0", + "@umbraco-ui/uui-menu-item": "1.8.0", + "@umbraco-ui/uui-modal": "1.8.0", + "@umbraco-ui/uui-pagination": "1.8.0", + "@umbraco-ui/uui-popover": "1.8.0", + "@umbraco-ui/uui-popover-container": "1.8.0", + "@umbraco-ui/uui-progress-bar": "1.8.0", + "@umbraco-ui/uui-radio": "1.8.0", + "@umbraco-ui/uui-range-slider": "1.8.0", + "@umbraco-ui/uui-ref": "1.8.0", + "@umbraco-ui/uui-ref-list": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0", + "@umbraco-ui/uui-ref-node-data-type": "1.8.0", + "@umbraco-ui/uui-ref-node-document-type": "1.8.0", + "@umbraco-ui/uui-ref-node-form": "1.8.0", + "@umbraco-ui/uui-ref-node-member": "1.8.0", + "@umbraco-ui/uui-ref-node-package": "1.8.0", + "@umbraco-ui/uui-ref-node-user": "1.8.0", + "@umbraco-ui/uui-scroll-container": "1.8.0", + "@umbraco-ui/uui-select": "1.8.0", + "@umbraco-ui/uui-slider": "1.8.0", + "@umbraco-ui/uui-symbol-expand": "1.8.0", + "@umbraco-ui/uui-symbol-file": "1.8.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.8.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.8.0", + "@umbraco-ui/uui-symbol-folder": "1.8.0", + "@umbraco-ui/uui-symbol-lock": "1.8.0", + "@umbraco-ui/uui-symbol-more": "1.8.0", + "@umbraco-ui/uui-symbol-sort": "1.8.0", + "@umbraco-ui/uui-table": "1.8.0", + "@umbraco-ui/uui-tabs": "1.8.0", + "@umbraco-ui/uui-tag": "1.8.0", + "@umbraco-ui/uui-textarea": "1.8.0", + "@umbraco-ui/uui-toast-notification": "1.8.0", + "@umbraco-ui/uui-toast-notification-container": "1.8.0", + "@umbraco-ui/uui-toast-notification-layout": "1.8.0", + "@umbraco-ui/uui-toggle": "1.8.0", + "@umbraco-ui/uui-visually-hidden": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-action-bar": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-action-bar/-/uui-action-bar-1.8.0.tgz", + "integrity": "sha512-IRs42chstgXFo5b3i0j80Emt+uZSt/WmDDv7gTtB768FL1C+k0BR5sYVleEmUdkfCOv+WIVo1FAqd+9CPFkDDw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button-group": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-avatar": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar/-/uui-avatar-1.8.0.tgz", + "integrity": "sha512-ek6SFYEvEbu1Jf1FVrqBDHuWqCnekkU1hm4XDHEpEyhPE5OOC70SyYLB6brT0kvgBE0QKB2txYu7u8ZbWzy+OQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-avatar-group": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-avatar-group/-/uui-avatar-group-1.8.0.tgz", + "integrity": "sha512-AS6+GzeoAOS6vuZ6okP30iik8cvYPjBvoWtSYcnV0gScw52FIg9ak+j5L+rQHzE8LCqT8c6RE63HsAsJe7f6oA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-avatar": "1.8.0", + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-badge": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-badge/-/uui-badge-1.8.0.tgz", + "integrity": "sha512-mKHkkXIwN7oUybeQo5J5TOgqghinJH5gE9lJwOemNCy/oiV/TeYHOr7MqHxIJ+13Nwl9O6JbSRWbPbOD9TSkVw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-base": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-base/-/uui-base-1.8.0.tgz", + "integrity": "sha512-LIPS3sfgOr/cgpDueTqpX+t6Bw0BpNISQSrAeyC+c6X0WiahKLuwob6UXSnefh9j5xIYa5+GY1gEUDgI4wlRhg==", + "dev": true, + "peer": true, + "peerDependencies": { + "lit": ">=2.8.0" + } + }, + "node_modules/@umbraco-ui/uui-boolean-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-boolean-input/-/uui-boolean-input-1.8.0.tgz", + "integrity": "sha512-6GqzuALrzrJIWIAdsYAau9t3sxYL0P+OKSKpcvj+4/DkbhbWjk54CtVFyWBAzYa9LhZHauGl2VYzxSvmGWARSA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-box": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-box/-/uui-box-1.8.0.tgz", + "integrity": "sha512-/j/69od/uWd1Utb2IzU5pq5cvKpsq0cV4F+pS9e6HejzpcVUCz1AtdKNQvgpyOzd/oS9r8Z6pYL/V/gEydyqwg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-css": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-breadcrumbs": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-breadcrumbs/-/uui-breadcrumbs-1.8.0.tgz", + "integrity": "sha512-Hv5NAfRzfaXcDYcuribjpaooZk1LVcHNTaLwoxVAUN64sufYS8kfw0sN8+jsacumUP3rpy0XgR9Ic37JUoIkBw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-button": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button/-/uui-button-1.8.0.tgz", + "integrity": "sha512-M0pLzpGt2CuzQAHqiHQwVdzFOyb9EznCT7b+YMQOlJCMfeVmN2KBF2xUlfb/3M2LVDukTHyGHzqaWj8Lj6YUbA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-button-group": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-group/-/uui-button-group-1.8.0.tgz", + "integrity": "sha512-5K/cvrOWvRmoXByuI1illF2e9sCzzegmlEpS4XbVk1XW/6quzRJpXSCrY+awj01kFrxB+UC8mB1DIECHKNyeVg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-button-inline-create": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-button-inline-create/-/uui-button-inline-create-1.8.0.tgz", + "integrity": "sha512-smSZKMG0cAp+BTZe0wRaYotcQElja8gqznGaIyuGuWvvpvWEiuWMC9xjMytFNvaawCN1+uLc5fdcCArPmFjH+w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-card": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card/-/uui-card-1.8.0.tgz", + "integrity": "sha512-wwHaDbwDmragvIhhAtv/D2Ys47kXFPBKvE+/ahofXUygjTGbmjbKJ57Qfo6x8sD1BM3bSTDU6gUWaf4s0/D3WQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-card-block-type": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-block-type/-/uui-card-block-type-1.8.0.tgz", + "integrity": "sha512-8Ydat2K4LipsQaCEhDTN4DeVHiqOCdEOY4Z43XCf6bTU91lNPGdagtC0ZE9b4M28E03ou4E19Ms7o2m59g0OWw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-card": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-card-content-node": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-content-node/-/uui-card-content-node-1.8.0.tgz", + "integrity": "sha512-R0SYtKk5Z+on0xQ0cD1z2z43mSTgYi7sKtQDrhwoP/sWbp9xIS2wPOO+PoMiUonwXPo4JuSZ9ghgT4HzsQce1A==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-card": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-card-media": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-media/-/uui-card-media-1.8.0.tgz", + "integrity": "sha512-C1xsdO9mWJ/gzE8nLfF2k5NfpFzJ2yMQYzJVtov3s9C33iy6NVq7OM67o+QugCqDuwwYSkonjgNJLHTav78KVg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-card": "1.8.0", + "@umbraco-ui/uui-symbol-file": "1.8.0", + "@umbraco-ui/uui-symbol-folder": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-card-user": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-card-user/-/uui-card-user-1.8.0.tgz", + "integrity": "sha512-b6LiMCl/oFaW6MnPXBMCPqlVDHF/TT3LByQOTJ8rE+WHzY/zE9toVLy/LccxB62Ur/Hz7XakhU8xHaugH+zs3w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-avatar": "1.8.0", + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-card": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-caret": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-caret/-/uui-caret-1.8.0.tgz", + "integrity": "sha512-LxS0vLKZYNKsef/FgNXFh/CBauf+6Dgac+n5VyCu6FElh8UTP+NOeAA/4KEVSJh8gThJ0Fmb8qoMSFop+41wLg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-checkbox": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-checkbox/-/uui-checkbox-1.8.0.tgz", + "integrity": "sha512-Gf/kZ4q5SuLNEEfcL1/YRzcOI5CZTsMyo2+9LuksCZqRzWtxoo1meB42GZRjE4GTCFZfQOr4Ayz7ZNRaizuM+Q==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-boolean-input": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-color-area": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-area/-/uui-color-area-1.8.0.tgz", + "integrity": "sha512-V3+iph2Vq58T9f4FoJvxsjq0LpH5VJhC2P2VkAWvMO1m528QOULDP+b5csYWH1pF78RxSZ5Lm042Dnw9XOqN2w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "colord": "^2.9.3" + } + }, + "node_modules/@umbraco-ui/uui-color-picker": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-picker/-/uui-color-picker-1.8.0.tgz", + "integrity": "sha512-DQtKN4ivIRx54SYUhxdlbFf5ibmnp5/mscOiC98HObYVTeM9ZJUrKfFGTU9Qfekz3q+rPzzv7eec8E0Zb6qfwg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-popover-container": "1.8.0", + "colord": "^2.9.3" + } + }, + "node_modules/@umbraco-ui/uui-color-slider": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-slider/-/uui-color-slider-1.8.0.tgz", + "integrity": "sha512-DHCFX0JZXqI6wp2fRe+lOd9TAJVzTC9ghDNP+qMGattsxRnTf/pxoYucW7F5ee/ggiBIsS8i47kFa2wDmausiA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-color-swatch": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatch/-/uui-color-swatch-1.8.0.tgz", + "integrity": "sha512-z9RQ0R5E9SErqiY1/kU6Rnp+LQBM119OKqAexHo32cM/9iyzLIEUY4CwzCYE9/ogeXDgljXLTGX21jddCNCv9A==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0", + "colord": "^2.9.3" + } + }, + "node_modules/@umbraco-ui/uui-color-swatches": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-color-swatches/-/uui-color-swatches-1.8.0.tgz", + "integrity": "sha512-qCHYtHPhPsubrVn/cydmigbAde0fc+kJC7ZBxCcuJjyP+wiUhq2/d6dSfYumCcVw1N3Hyj7BRJ/8ZedUIZQ5/w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-color-swatch": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-combobox": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox/-/uui-combobox-1.8.0.tgz", + "integrity": "sha512-FVc3PlBYU8S48Zr75pig+5YXh05R3wRKdLl41l7vFBDGGWsgjIsM+vJ6LaM+VoshnTzUTOVvKLE/N0FNTVUvrg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-combobox-list": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-popover-container": "1.8.0", + "@umbraco-ui/uui-scroll-container": "1.8.0", + "@umbraco-ui/uui-symbol-expand": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-combobox-list": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-combobox-list/-/uui-combobox-list-1.8.0.tgz", + "integrity": "sha512-3w353u7FdYNLCz6YV+Pk+lxlTeHd2H6rmxzwb+0BHCjbwaw9MLojbhE4JGTCpf/qtk54Xc8Dzg++aznKAYpbVw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-css": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-css/-/uui-css-1.8.0.tgz", + "integrity": "sha512-9o9OGUXQK8D9i/VSmH3gUTvH7se+4Ey22dSfbn4Jzcbe6AyGmxKocr/8eZXdkIYwNvK2aUIz/b7GIEbQc4utbA==", + "dev": true, + "peer": true, + "peerDependencies": { + "lit": ">=2.8.0" + } + }, + "node_modules/@umbraco-ui/uui-dialog": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog/-/uui-dialog-1.8.0.tgz", + "integrity": "sha512-QI/Oa7BJ7eEdHcy7hSuFhMPbbPitxnIb2BHVmQzy+YpBShrR3C1GW0FGcYFgJlI/S+jodf3A59VDnVL69gyliQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-css": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-dialog-layout": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-dialog-layout/-/uui-dialog-layout-1.8.0.tgz", + "integrity": "sha512-iv4wEfb6QhCOm8lxg/zH7yrJuVSEEBGgAN67qdvZ9Tu2SFzUCGTzrUcBXlL+U10cbIlq7j6KKvSQvE/IHX40Tg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-file-dropzone": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-dropzone/-/uui-file-dropzone-1.8.0.tgz", + "integrity": "sha512-Fpl/aGprUbcdPngB6xpR8/i7o8HKAWNCUze+N1pXiIVBRXmthEWO6fSm4+LkkkRoZwvYqQSaeu6Mw+Sj+MzHGA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-symbol-file-dropzone": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-file-preview": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-file-preview/-/uui-file-preview-1.8.0.tgz", + "integrity": "sha512-WhE/9klwIUjch6PxF+DuFlu+ql0h9YbefMxj/xU4rGUTE/dK4CgA7eVQ/YfAp+WrdtwUFHyp3fThyJvfrodSgA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-symbol-file": "1.8.0", + "@umbraco-ui/uui-symbol-file-thumbnail": "1.8.0", + "@umbraco-ui/uui-symbol-folder": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-form": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form/-/uui-form-1.8.0.tgz", + "integrity": "sha512-9NtavsRoh9X39ezzLtwwbK77mUecavcjxP58Jdba/2/6wXRx+vx7qsWV5Rn3OC9XG4tZi6VLFFKahbV8N/jgjw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-form-layout-item": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-layout-item/-/uui-form-layout-item-1.8.1.tgz", + "integrity": "sha512-QHBZayR4T49MAyS9N2jy1rgQ5Yk1JpwoWvWBpbI/WDE718zTjwUJxbLfukq+NnJx7Q1DplZ+IHTnHZkInMC2GQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-form-validation-message": "1.8.1" + } + }, + "node_modules/@umbraco-ui/uui-form-validation-message": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-form-validation-message/-/uui-form-validation-message-1.8.1.tgz", + "integrity": "sha512-o4WfGHRtV+DrN064DtzyljRkUGYMYEkLHxxbawLowpdmdwyvLByaGOkxfuEJvHgPnncR02//gM04EjulEbGitw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-icon": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon/-/uui-icon-1.8.0.tgz", + "integrity": "sha512-hub+KNcwiy+SKxEpI/ei3w1Ficr1SWxcLfwL67MOKS5YyB6RDwSl2FOXx+MkttTAO7PvGBbAgkiiXEkI/rxivg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-icon-registry": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry/-/uui-icon-registry-1.8.0.tgz", + "integrity": "sha512-+55qGgxOBlHmQerxIhKkKJYJgPwnXwsLOBVwF8oYIM1sV58bu7BrGQRUw/K0ytFavrFOb+Easkn62wqzisXsRQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-icon-registry-essential": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-icon-registry-essential/-/uui-icon-registry-essential-1.8.0.tgz", + "integrity": "sha512-6+bM30t3+YpknxIjcCHi07eS+MXMkDRP+GBfrUgULqH/EqnJN/OuwMzSdbwFuwzqxmC7thx74Zfl6+JBuIs9lw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon-registry": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-input": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input/-/uui-input-1.8.0.tgz", + "integrity": "sha512-abtQbWWDT+3uo4KVaU+ZbDVKSNtB8r0C/3L7Ql/7xJ2BNI2oSUSAcWoSV6V8Vx0DYh1XWlEQpfSAbk5Fdr7u4w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-input-file": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-file/-/uui-input-file-1.8.0.tgz", + "integrity": "sha512-20fXbIwjyLZWIVsqFt06ldQqA8sHEPm8Y0hmPwbb0nagYfjmIblIE1thT76JA95do7qcU50xGBN9mHt8KvPpQA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-action-bar": "1.8.0", + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-file-dropzone": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-input-lock": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-lock/-/uui-input-lock-1.8.0.tgz", + "integrity": "sha512-ZFBssrhCPrCiSfoS6Y9yA9u2dktzNDCRFQ95Z9nQEthhVm2okyqMS3daGz57Vdm4+LVB0HrqIjpEHC6dOqNTBg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-input": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-input-password": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-input-password/-/uui-input-password-1.8.0.tgz", + "integrity": "sha512-Ra7bR40GzjX11qobHsog2FPGd3FsPzgxPpGLFyMTHzDg2gchYU+KQKkmt6CTzGPgSFuN7DsEW0BMnFnWJ+2moQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0", + "@umbraco-ui/uui-input": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-keyboard-shortcut": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-keyboard-shortcut/-/uui-keyboard-shortcut-1.8.0.tgz", + "integrity": "sha512-XBO06bZbo7H39k33pm8EoWxm9MP/NXh7W430dLqB5E3q1EOO24PQw2voLsOyegVM3IqgKyMBTO7xHR8NmeZkyg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-label": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-label/-/uui-label-1.8.0.tgz", + "integrity": "sha512-Qj3CtfVIv3rVIanTbMvg6UAB2faWGu2mMtvVfmr9kkEP9MGfmA/xgQN94lKlDI7ic54FVkCV4N+1kA5yNkeDSQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-loader": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader/-/uui-loader-1.8.0.tgz", + "integrity": "sha512-CL+n3Icp4ugfn7SBbhbYC4WTBQ+kT27WwJIesOcjw2lmt2l20RQUyBBbZAABx2ayyDuvIzEWnFEzWW8VyVworw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-loader-bar": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-bar/-/uui-loader-bar-1.8.0.tgz", + "integrity": "sha512-KuUtQ4r/wkbVl4IJVgUb9DCXvV1yvupDw6AAnWuOu7zexuzPfrl32cKBLlnNmhGqnEGcQonX6jf24Lf8T7W6Nw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-loader-circle": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-loader-circle/-/uui-loader-circle-1.8.0.tgz", + "integrity": "sha512-L5RyH10Es/stG7CzNzS0bKOzCY+zLSwmqyh0dc8HwzZCvc6XtFHV0KgcxMjmtWWulLsQPgzIOIigf3wefr2VNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-menu-item": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-menu-item/-/uui-menu-item-1.8.0.tgz", + "integrity": "sha512-0zEMhcw35Evw7cEq1W0GYNVCzIorTVMzFquU7Sg2QiZmk3eiF6HvkF/gHr4d0WR7HvztM1k/eVm+RTs6zp+96g==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-loader-bar": "1.8.0", + "@umbraco-ui/uui-symbol-expand": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-modal": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-modal/-/uui-modal-1.8.0.tgz", + "integrity": "sha512-Cqd4pabMLnpnMEa35MKOwXCKQ+5okHYWdvdFRA8x1HqI3AEcz4FNx48nXVH94vhbELv8+fWFAPfrr1v0rvjK6w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-pagination": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-pagination/-/uui-pagination-1.8.0.tgz", + "integrity": "sha512-SvnWMzbQcTDEN+XQJ0/lVCJ1wL+2L7LA9pfSxJgXIj/pB55Pf3Tt3zMnW8B7RT7i/74atMufaqSSKElZrcPfHQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-button-group": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-popover": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover/-/uui-popover-1.8.0.tgz", + "integrity": "sha512-bHERyYItLAvtWbpOdPgmuPFgYs2TuKImm3h04DtQRatYP4dq8wg0tftVyZK3k9TVfLMvStOy2EyzybTD+1ZmBg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-popover-container": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-popover-container/-/uui-popover-container-1.8.0.tgz", + "integrity": "sha512-FHaX4sIa7q4HTVzMr9w3Z6zuunPuDROnHjVS6QkovqHLEgQjeTQB8hGAxN6cd3QsFbgbtC9fzBo8zTn9yxJLmA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-progress-bar": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-progress-bar/-/uui-progress-bar-1.8.0.tgz", + "integrity": "sha512-WX+YjiH0HCljtzTImR6Q8bf06wm+jcCgEOE10pMRri0r4nyBCAN7Zms23sHj6rR+S/oxoYcuREqdWXWP4eRfFA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-radio": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-radio/-/uui-radio-1.8.0.tgz", + "integrity": "sha512-3kPWyc0ZbtAIeOHxAKudSslomg1zKy4RMgrfDeNumQhuCei0VWhVn76Xyfu/Gl2n4XnLIzZY3zv4XCaxwWvJZw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-range-slider": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-range-slider/-/uui-range-slider-1.8.0.tgz", + "integrity": "sha512-ThDfLvxRMfP93HQobgLaglR860vQiwCM63PXp2igXWZl69ikDDa7HuraupEpmdL13VLKAv5L1BMue1ItC1x2MA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref/-/uui-ref-1.8.0.tgz", + "integrity": "sha512-mcw7faAXeG/2QfLeuYsB950seU0FQH5Pd+aSfi6zwgWkCasxKWLrlslVTP1U5zD5WFkNt4ls6RnMGZhN6bq7mQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-list": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-list/-/uui-ref-list-1.8.0.tgz", + "integrity": "sha512-9iiTYVyw+dpO1gEB2oMJSe2DOVvA3a2uY5FLwkRgpAjvzbhD9hhyGLcVgtvM1rxUYc9SvJbGJXk2tPY4Nit3pA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node/-/uui-ref-node-1.8.0.tgz", + "integrity": "sha512-KOGy10QhPUQFNFNPkmqro1YqFg5Y5b2N/DwkcYP/xyu4Kc1f5FuE+Uo5L41i2KdbDP6O+Tz5gsU6HpPvpEAc7Q==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-ref": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-data-type": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-data-type/-/uui-ref-node-data-type-1.8.0.tgz", + "integrity": "sha512-x7PicpHE7PPZrVDojKO5ornG7HZoRB9/pjCZWV+8wxPznyGmFvcJpbRQLdIVvvXVkL1a0c4uLY2CsUO+K52Rbg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-document-type": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-document-type/-/uui-ref-node-document-type-1.8.0.tgz", + "integrity": "sha512-QjeC8DvgA/WFb3wOUXMOALK5SoKeDAvYqNp1wNvU5AMxJY+5CTfi6JNWKZsI4K+Mh3lfzPP+HaWx5MTwt5epNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-form": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-form/-/uui-ref-node-form-1.8.0.tgz", + "integrity": "sha512-sm0xSru6pv8yK9qepzF5Wzzd1jarMBZFtzgqw5H65pGTP6pNhNG4GIkeXLfd2TH9g3e6biJNKAOFJ5w8Tz4O9A==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-member": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-member/-/uui-ref-node-member-1.8.0.tgz", + "integrity": "sha512-z9l44zCER4KmNMSnCy6BFxXZ6awr/L405pJJJzqb3GAXsBip47pCzhYgxCbekB+xSY+E0hS1EuG1JJ5wn05yiQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-package": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-package/-/uui-ref-node-package-1.8.0.tgz", + "integrity": "sha512-bpWVBOm39tl5jY3Y+qn5dnikmnaAOrHEg2SBf/11d7aHOauEgDiJZQmM9BzjAeionaZrj4beYJw5szazaVkpDQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-ref-node-user": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-ref-node-user/-/uui-ref-node-user-1.8.0.tgz", + "integrity": "sha512-Z91Me56phGFyt/hJaRDnXrorlYVjrL6WaUWRDZAi+mqqThFriG1DVeaFEewZ1yeD3Hy6haHanDa7dtwm2FLsBQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-ref-node": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-scroll-container": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-scroll-container/-/uui-scroll-container-1.8.0.tgz", + "integrity": "sha512-PHxOZhsM53J3SQaXhH0euUwshr6Ka06iFEmtNKaqVe+nPwTjYHWHDuulvp7/qm/btjzSIrJqKgs02ft8wGqXwA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-select": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-select/-/uui-select-1.8.0.tgz", + "integrity": "sha512-xFXo7IlAtofZCWIqnhyThIjP8ORRwo6786fv5yarayhqlKeAwJRD5t6QtX4z5z/1b/zW11oF2GkJzrzp4KN4vw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-slider": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-slider/-/uui-slider-1.8.0.tgz", + "integrity": "sha512-qKgWyVzMKF8RVwwgpdMvKfCS3TEyMbZj/ZKClgTJJfs+3r8Q002F7irx7Lgh+mDww+jLsuMtG/cu0xSXU4HC4w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-expand": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-expand/-/uui-symbol-expand-1.8.0.tgz", + "integrity": "sha512-AfpoR4dTOL4gEfP9lnEVymU3mlNfjFSuk8TGbqy0jVMTMbYeol5Bcl6jJFqqPd1npfgT7FPZH9zrMkcFogfSSw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-file": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file/-/uui-symbol-file-1.8.0.tgz", + "integrity": "sha512-/2O0TNl+Sx/cCy2kQlSCOujvRwz+Rxg9JxyMX5Vc14ZqrVJ4FsD2S/jJWLtE2YJ+EtLoc15Zzw2GogZO7aBcLQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-file-dropzone": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-dropzone/-/uui-symbol-file-dropzone-1.8.0.tgz", + "integrity": "sha512-n2QRKTEnvQQgiyTQ7uVbz7XsGL0HRwaEtLqEMbaON6oYCsGWFFsbp8QqyHdB8iBQSrlV9I1J4sS0e5Ry+W25YQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-file-thumbnail": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-file-thumbnail/-/uui-symbol-file-thumbnail-1.8.0.tgz", + "integrity": "sha512-KdFOrfVIwtjavoa+S5ro1gi2Er8IPqXnY6gpTRpAgfO/f+/ZRg6AwPKn4SCc7QqJ8ThHpsg8wki8WGiK04rfbA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-folder": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-folder/-/uui-symbol-folder-1.8.0.tgz", + "integrity": "sha512-g7FIonq/5wHH2+e/+DhB+t3E4wu7dM4MrKxLsP6b8JmYz7Y0t9OlTBT5J+i3P8YnJKYY6i5V5Eip4QNWJqC+sQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-lock": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-lock/-/uui-symbol-lock-1.8.0.tgz", + "integrity": "sha512-+/K59hTkBJr6bYOrTgPtvZEVsr59MPNwvIHhGm685WZPZrNA9dGPZrO9vnw1eyNq70XYuHkiNkmKUmna9mQmIA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-more": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-more/-/uui-symbol-more-1.8.0.tgz", + "integrity": "sha512-BSWQ05XYJSS6WKpA6//QnSnMehV5py5j8bxl7bZzmrthr2SwyejwS+pGYq7rTqWw7BBk1mA8I7Zkl+kVph/7+g==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-symbol-sort": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-symbol-sort/-/uui-symbol-sort-1.8.0.tgz", + "integrity": "sha512-+IFJYlPsUvJYNDc8sWB4zan/dTCCj4vkqwXALW3xLSxpsKSvtSvXPzXK/i4YwaT4Azx4hfrWIW2cz6/h5JDonA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-table": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-table/-/uui-table-1.8.0.tgz", + "integrity": "sha512-nbRoValRn17SadfpGKKT1RfyoRlCLhvij2BRMw1KyldztWlWGozPQSDjqhcEPSzJZCrNV76nIMbg5FLfsTl4iA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-tabs": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tabs/-/uui-tabs-1.8.0.tgz", + "integrity": "sha512-xnZvjjmRHJbyC9bd6LMYfHA8Jjb51GTJuFAd4j4S9NVZYomQDBFl7IKVWtUFzQvVzk93zJHVSWO8vmtNLQZreg==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-popover-container": "1.8.0", + "@umbraco-ui/uui-symbol-more": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-tag": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-tag/-/uui-tag-1.8.0.tgz", + "integrity": "sha512-5Dt7EsvACfs75bsncIDvqitXYub2Rfntbrc3gzXLHjqOQy2YBL5s/HNGz3xsf5msKuDAR0dmTyxhItaDAi7EkQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-textarea": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-textarea/-/uui-textarea-1.8.0.tgz", + "integrity": "sha512-WYWiD3x1DXbsAALmTT2txoeeqiZ9J/FlkTGL1Wasu89jfm8bAgxUG5wuoa8SL4r79rAF+RUDrJPygeUqDm0N8A==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-toast-notification": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification/-/uui-toast-notification-1.8.0.tgz", + "integrity": "sha512-62q36/jggXp+GdRSzseQ7d63hUIXtHIXe/5bnSSHXcxxIcnbf9Sy3pkBkOYM7CXgBUUwt9T9IHLZ6eGw/6K9Xw==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-button": "1.8.0", + "@umbraco-ui/uui-css": "1.8.0", + "@umbraco-ui/uui-icon": "1.8.0", + "@umbraco-ui/uui-icon-registry-essential": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-toast-notification-container": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-container/-/uui-toast-notification-container-1.8.0.tgz", + "integrity": "sha512-NdILHgGvKFF+JZGejTJnXt1LdJIl/MxmPUj6+rvEGCO2SDhZmIOHjnIohT8HFytxmJqyWJpryPQjo6KJezRVbQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-toast-notification": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-toast-notification-layout": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toast-notification-layout/-/uui-toast-notification-layout-1.8.0.tgz", + "integrity": "sha512-NyGFv3kAcU8XMxLAyDhy3dt1oIHOwbAYO5+Utm4CFAAvcJzVTNn948Sp0dTdcfSjXjZG+3Ufv/Qb/OpcrybJ/w==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-css": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-toggle": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-toggle/-/uui-toggle-1.8.0.tgz", + "integrity": "sha512-PNk2qeaL7bJXnSkG0T1J5pheNy7yTeaVbdyXuL/9fkoIdb9IkD/h6XLE9niiyWYF1xrdjpgAKZ32u0Oc4qXVyA==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0", + "@umbraco-ui/uui-boolean-input": "1.8.0" + } + }, + "node_modules/@umbraco-ui/uui-visually-hidden": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@umbraco-ui/uui-visually-hidden/-/uui-visually-hidden-1.8.0.tgz", + "integrity": "sha512-3a4/B2uM3YfXjI9dyfSL2Z47ziwW7HuYoozNderKO/I7l0CgxgoHIOwF1sRb3QgOlsFhhfeKdndvU7SbD6tazQ==", + "dev": true, + "peer": true, + "dependencies": { + "@umbraco-ui/uui-base": "1.8.0" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", + "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz", + "integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "peer": true + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/c12": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-1.10.0.tgz", + "integrity": "sha512-0SsG7UDhoRWcuSvKWHaXmu5uNjDCDN3nkQLRL4Q42IlFy+ze58FcCoI3uPwINXinkz7ZinbhEgyzYFw9u9ZV8g==", + "dev": true, + "dependencies": { + "chokidar": "^3.6.0", + "confbox": "^0.1.3", + "defu": "^6.1.4", + "dotenv": "^16.4.5", + "giget": "^1.2.1", + "jiti": "^1.21.0", + "mlly": "^1.6.1", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "perfect-debounce": "^1.0.0", + "pkg-types": "^1.0.3", + "rc9": "^2.1.1" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", + "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "dev": true, + "dependencies": { + "consola": "^3.2.3" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "peer": true + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/confbox": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz", + "integrity": "sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==", + "dev": true + }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", + "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "dev": true + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/destr": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.3.tgz", + "integrity": "sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ==", + "dev": true + }, + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom5": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dom5/-/dom5-3.0.1.tgz", + "integrity": "sha512-JPFiouQIr16VQ4dX6i0+Hpbg3H2bMKPmZ+WZgBOSSvOPx9QHwwY8sPzeM2baUtViESYto6wC2nuZOMC/6gulcA==", + "dev": true, + "dependencies": { + "@types/parse5": "^2.2.34", + "clone": "^2.1.0", + "parse5": "^4.0.0" + } + }, + "node_modules/dom5/node_modules/parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, + "node_modules/dompurify": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.5.tgz", + "integrity": "sha512-lwG+n5h8QNpxtyrJW/gJWckL+1/DQiYMX8f7t8Z2AZTPw1esVrqjI63i7Zc2Gz0aKzLVMYC1V1PL/ky+aY/NgA==", + "dev": true, + "peer": true + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/element-internals-polyfill": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/element-internals-polyfill/-/element-internals-polyfill-1.3.11.tgz", + "integrity": "sha512-SQLQNVY4wMdpnP/F/HtalJbpEenQd46Avtjm5hvUdeTs3QU0zHFNX5/AmtQIPPcfzePb0ipCkQGY4GwYJIhLJA==", + "dev": true, + "peer": true + }, + "node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/enhanced-resolve": { + "version": "5.17.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", + "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz", + "integrity": "sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "enhanced-resolve": "^5.12.0", + "eslint-module-utils": "^2.7.4", + "fast-glob": "^3.3.1", + "get-tsconfig": "^4.5.0", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", + "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-lit": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.14.0.tgz", + "integrity": "sha512-J4w+CgO31621GreLFCdTUbTr5yeV2/RJ/M0myw0dykD5p9FGGIRLityQiNa6SG+JpVbmeQTQPJy4pNFmiurJ/w==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "^1.2.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "eslint": ">= 5" + } + }, + "node_modules/eslint-plugin-lit-a11y": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit-a11y/-/eslint-plugin-lit-a11y-4.1.3.tgz", + "integrity": "sha512-ndfGZU0RFKp9xqm17f94sQzjgVchDtYL1z/N0qG0otQiDtia1EUVCdd1eMlCeeUe6LTtfjV4iAsNU4r0bVB/Yw==", + "dev": true, + "dependencies": { + "@thepassle/axobject-query": "^4.0.0", + "aria-query": "^5.1.3", + "axe-core": "^4.3.3", + "dom5": "^3.0.1", + "emoji-regex": "^10.2.1", + "eslint-plugin-lit": "^1.10.1", + "eslint-rule-extender": "0.0.1", + "language-tags": "^1.0.5", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "~1.2.0" + }, + "peerDependencies": { + "eslint": ">= 5" + } + }, + "node_modules/eslint-plugin-lit-a11y/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/eslint-plugin-local-rules": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-local-rules/-/eslint-plugin-local-rules-2.0.1.tgz", + "integrity": "sha512-AJhGd+GcI5r2dbjiGPixM8jnBl0XFxqoVbqzwKbYz+nTk+Cj5dNE3+OlhC176bl5r25KsGsIthLi1VqIW5Ga+A==", + "dev": true + }, + "node_modules/eslint-plugin-wc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.1.0.tgz", + "integrity": "sha512-s/BGOtmpgQ2yifR6EC1OM9t0DwYLgg4ZAL07Kw4eXvBb5TYaPafI+65tswvnZvhH8FqcjERLbBZPPvYsvinkfg==", + "dev": true, + "dependencies": { + "is-valid-element-name": "^1.0.0", + "js-levenshtein-esm": "^1.2.0" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-rule-extender": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/eslint-rule-extender/-/eslint-rule-extender-0.0.1.tgz", + "integrity": "sha512-F0j1Twve3lamL3J0rRSVAynlp58sDPG39JFcQrM+u9Na7PmCgiPHNODh6YE9mduaGcsn3NBqbf6LZRj0cLr8Ng==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kaicataldo" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", + "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.7.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", + "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/giget": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.3.tgz", + "integrity": "sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==", + "dev": true, + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.3", + "nypm": "^0.3.8", + "ohash": "^1.1.3", + "pathe": "^1.1.2", + "tar": "^6.2.0" + }, + "bin": { + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "engines": { + "node": ">=16.17.0" + } + }, + "node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/internal-slot": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", + "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.0", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", + "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", + "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "dev": true, + "dependencies": { + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", + "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", + "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-valid-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-element-name/-/is-valid-element-name-1.0.0.tgz", + "integrity": "sha512-GZITEJY2LkSjQfaIPBha7eyZv+ge0PhBR7KITeCCWvy7VBQrCUdFkvpI+HrAPQjVtVjy1LvlEkqQTHckoszruw==", + "dev": true, + "dependencies": { + "is-potential-custom-element-name": "^1.0.0" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jiti": { + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-levenshtein-esm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", + "integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lit": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.1.4.tgz", + "integrity": "sha512-q6qKnKXHy2g1kjBaNfcoLlgbI3+aSOZ9Q4tiGa9bGYXq5RBXxkVTqTIVmP2VWMp29L4GyvCFm8ZQ2o56eUAMyA==", + "dev": true, + "peer": true, + "dependencies": { + "@lit/reactive-element": "^2.0.4", + "lit-element": "^4.0.4", + "lit-html": "^3.1.2" + } + }, + "node_modules/lit-element": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.6.tgz", + "integrity": "sha512-U4sdJ3CSQip7sLGZ/uJskO5hGiqtlpxndsLr6mt3IQIjheg93UKYeGQjWMRql1s/cXNOaRrCzC2FQwjIwSUqkg==", + "dev": true, + "peer": true, + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.2.0", + "@lit/reactive-element": "^2.0.4", + "lit-html": "^3.1.2" + } + }, + "node_modules/lit-html": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.4.tgz", + "integrity": "sha512-yKKO2uVv7zYFHlWMfZmqc+4hkmSbFp8jgjdZY9vvR9jr4J8fH6FUMXhr+ljfELgmjpvlF7Z1SJ5n5/Jeqtc9YA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/marked": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.2.tgz", + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", + "dev": true, + "peer": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mlly": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz", + "integrity": "sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==", + "dev": true, + "dependencies": { + "acorn": "^8.11.3", + "pathe": "^1.1.2", + "pkg-types": "^1.1.1", + "ufo": "^1.5.3" + } + }, + "node_modules/monaco-editor": { + "version": "0.48.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.48.0.tgz", + "integrity": "sha512-goSDElNqFfw7iDHMg8WDATkfcyeLTNpBHQpO8incK6p5qZt5G/1j41X0xdGzpIkGojGXM+QiRQyLjnfDVvrpwA==", + "dev": true, + "peer": true + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-fetch-native": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.4.tgz", + "integrity": "sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/nypm": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.3.8.tgz", + "integrity": "sha512-IGWlC6So2xv6V4cIDmoV0SwwWx7zLG086gyqkyumteH2fIgCAM4nDVFB2iDRszDvmdSVW9xb1N+2KjQ6C7d4og==", + "dev": true, + "dependencies": { + "citty": "^0.1.6", + "consola": "^3.2.3", + "execa": "^8.0.1", + "pathe": "^1.1.2", + "ufo": "^1.4.0" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": "^14.16.0 || >=16.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", + "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", + "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ohash": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz", + "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz", + "integrity": "sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==", + "dev": true, + "dependencies": { + "confbox": "^0.1.7", + "mlly": "^1.7.0", + "pathe": "^1.1.2" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", + "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "dev": true, + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz", + "integrity": "sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "set-function-name": "^2.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", + "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "get-intrinsic": "^1.2.4", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz", + "integrity": "sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-regex": "^1.1.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", + "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", + "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tinymce": { + "version": "6.8.4", + "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-6.8.4.tgz", + "integrity": "sha512-okoJyxuPv1gzASxQDNgQbnUXOdAIyoOSXcXcZZu7tiW0PSKEdf3SdASxPBupRj+64/E3elHwVRnzSdo82Emqbg==", + "dev": true, + "peer": true + }, + "node_modules/tinymce-i18n": { + "version": "24.6.24", + "resolved": "https://registry.npmjs.org/tinymce-i18n/-/tinymce-i18n-24.6.24.tgz", + "integrity": "sha512-FToQhgKzZLqEg+twKVjUcS8gPJbZprOtiyGhGHhxGZMeqJITgbD0imc2QV7cZ82cZbHTBOkK2aOvgmbhk5uaTw==", + "dev": true, + "peer": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", + "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/tsconfck": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.0.tgz", + "integrity": "sha512-CMjc5zMnyAjcS9sPLytrbFmj89st2g+JYtY/c02ug4Q+CZaAtCgbyviI0n1YvjZE/pzoc6FbNsINS13DOL1B9w==", + "dev": true, + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true, + "peer": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", + "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", + "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", + "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", + "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-proto": "^1.0.3", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.3.tgz", + "integrity": "sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==", + "dev": true + }, + "node_modules/uglify-js": { + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.18.0.tgz", + "integrity": "sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==", + "dev": true, + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.1.tgz", + "integrity": "sha512-XBmSKRLXLxiaPYamLv3/hnP/KXDai1NDexN0FpkTaZXTfycHvkRHoenpgl/fvuK/kPbB6xAgoyiryAhQNxYmAQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-4.3.2.tgz", + "integrity": "sha512-0Vd/a6po6Q+86rPlntHye7F31zA2URZMbH8M3saAZ/xR9QoGN/L21bxEGfXdWmFdNkqPpRdxFT7nmNe12e9/uA==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.15.tgz", + "integrity": "sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/PersonalisationGroups/Client/package.json b/PersonalisationGroups/Client/package.json new file mode 100644 index 0000000..fc616bf --- /dev/null +++ b/PersonalisationGroups/Client/package.json @@ -0,0 +1,37 @@ +{ + "name": "personalisation-groups", + "private": true, + "version": "0.0.0", + "type": "module", + "engines": { + "node": ">=20.9 <21", + "npm": ">=10.1 < 11" + }, + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "watch": "vite build --watch", + "generate:api": "openapi-ts", + "lint:errors": "npm run lint -- --quiet", + "lint:fix": "npm run lint -- --fix", + "lint": "eslint src" + }, + "devDependencies": { + "@hey-api/openapi-ts": "^0.46.3", + "@typescript-eslint/eslint-plugin": "^7.13.0", + "@typescript-eslint/parser": "^7.13.0", + "@umbraco-cms/backoffice": "^14.0.0", + "eslint": "^8.56.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-typescript": "^3.6.1", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-lit": "^1.11.0", + "eslint-plugin-lit-a11y": "^4.1.1", + "eslint-plugin-local-rules": "^2.0.1", + "eslint-plugin-wc": "^2.0.4", + "typescript": "^5.2.2", + "vite": "^5.2.0", + "vite-tsconfig-paths": "^4.3.1" + } +} diff --git a/PersonalisationGroups/Client/public/umbraco-package.json b/PersonalisationGroups/Client/public/umbraco-package.json new file mode 100644 index 0000000..1e5a2e8 --- /dev/null +++ b/PersonalisationGroups/Client/public/umbraco-package.json @@ -0,0 +1,13 @@ +{ + "id": "UmbracoPersonalisationGroups", + "name": "Personalisation Groups", + "version": "4.0.0-rc1", + "extensions": [ + { + "name": "Umbraco EntryPoint", + "alias": "PersonalisationGroups.EntryPoint", + "type": "entryPoint", + "js": "/App_Plugins/PersonalisationGroups/personalisation-groups.js" + } + ] +} diff --git a/PersonalisationGroups/Client/src/index.ts b/PersonalisationGroups/Client/src/index.ts new file mode 100644 index 0000000..fe0f286 --- /dev/null +++ b/PersonalisationGroups/Client/src/index.ts @@ -0,0 +1,31 @@ +import type { UmbEntryPointOnInit } from "@umbraco-cms/backoffice/extension-api"; +import { UMB_AUTH_CONTEXT } from "@umbraco-cms/backoffice/auth"; +import { OpenAPI } from "../generated/index.js"; +import { ManifestTypes } from "@umbraco-cms/backoffice/extension-registry"; +import { manifests as modalManifests } from "./modal/manifests.js"; +import { manifests as propertyEditorManifests } from "./property-editor/manifests.js"; +import { manifests as translatorManifests } from "./translator/manifests.js"; + +export * from "./modal/index.js"; +export * from "./property-editor/index.js"; +export * from "./translator/index.js"; + +const manifests: Array = [ + ...modalManifests, + ...propertyEditorManifests, + ...translatorManifests, +]; + +export const onInit: UmbEntryPointOnInit = (_host, extensionRegistry) => { + extensionRegistry.registerMany(manifests); + + _host.consumeContext(UMB_AUTH_CONTEXT, async (auth) => { + if (!auth) return; + + const umbOpenApi = auth.getOpenApiConfiguration(); + OpenAPI.BASE = umbOpenApi.base; + OpenAPI.TOKEN = umbOpenApi.token; + OpenAPI.WITH_CREDENTIALS = umbOpenApi.withCredentials; + OpenAPI.CREDENTIALS = umbOpenApi.credentials; + }); +}; diff --git a/PersonalisationGroups/Client/src/modal/edit-detail-definition-modal.element.ts b/PersonalisationGroups/Client/src/modal/edit-detail-definition-modal.element.ts new file mode 100644 index 0000000..219ffb2 --- /dev/null +++ b/PersonalisationGroups/Client/src/modal/edit-detail-definition-modal.element.ts @@ -0,0 +1,84 @@ +import { + customElement, + html, + state, +} from "@umbraco-cms/backoffice/external/lit"; +import { UmbModalBaseElement } from "@umbraco-cms/backoffice/modal"; +import { EditDetailDefinitionModalData, EditDetailDefinitionModalValue } from "./edit-detail-definition-modal.token"; +import { UmbPropertyDatasetElement, UmbPropertyValueData } from "@umbraco-cms/backoffice/property"; + +const elementName = "edit-detail-definition-modal"; + +@customElement(elementName) +export class EditDetailDefinitionModalElement extends UmbModalBaseElement< + EditDetailDefinitionModalData, + EditDetailDefinitionModalValue +> { + + constructor() { + super(); + } + + @state() + _definitionAsPropertyValues: Array = []; + + #getDefinitionAsPropertyValues() { + const values: Array = []; + values.push({ + alias: "definition", + value: this.value.detail.definition, + }); + + return values; + } + + #onDefinitionChange(e: CustomEvent) { + const value = (e.target as UmbPropertyDatasetElement).value; + this._definitionAsPropertyValues = value; + + const detail = structuredClone(this.value.detail); + detail.definition = this._definitionAsPropertyValues[0].value?.toString() ?? ""; + this.modalContext?.updateValue({ detail }); + } + + render() { + return html` +
+ + + + + + +
+
+ + +
+
`; + } +} + +export default EditDetailDefinitionModalElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: EditDetailDefinitionModalElement; + } +} diff --git a/PersonalisationGroups/Client/src/modal/edit-detail-definition-modal.token.ts b/PersonalisationGroups/Client/src/modal/edit-detail-definition-modal.token.ts new file mode 100644 index 0000000..2fdbb1d --- /dev/null +++ b/PersonalisationGroups/Client/src/modal/edit-detail-definition-modal.token.ts @@ -0,0 +1,23 @@ +import { UmbModalToken } from "@umbraco-cms/backoffice/modal"; +import { PersonalisationGroupDetail } from "../types"; + +export const EDIT_DETAIL_DEFINITION_MODAL_ALIAS = "PersonalisationGroups.Modal.EditDetailDefinition"; + +export interface EditDetailDefinitionModalData { + criteriaName: string, + propertyEditorUiAlias: string, +} + +export interface EditDetailDefinitionModalValue { + detail: PersonalisationGroupDetail, +} + +export const EDIT_DETAIL_DEFINITION_MODAL = new UmbModalToken< +EditDetailDefinitionModalData, +EditDetailDefinitionModalValue +>(EDIT_DETAIL_DEFINITION_MODAL_ALIAS, { + modal: { + type: "sidebar", + size: "medium", + }, +}); diff --git a/PersonalisationGroups/Client/src/modal/index.ts b/PersonalisationGroups/Client/src/modal/index.ts new file mode 100644 index 0000000..677b36e --- /dev/null +++ b/PersonalisationGroups/Client/src/modal/index.ts @@ -0,0 +1,2 @@ +export * from "./edit-detail-definition-modal.element.js"; +export * from "./edit-detail-definition-modal.token.js"; diff --git a/PersonalisationGroups/Client/src/modal/manifests.ts b/PersonalisationGroups/Client/src/modal/manifests.ts new file mode 100644 index 0000000..f8eb117 --- /dev/null +++ b/PersonalisationGroups/Client/src/modal/manifests.ts @@ -0,0 +1,15 @@ +import type { ManifestModal } from "@umbraco-cms/backoffice/extension-registry"; +import { + EDIT_DETAIL_DEFINITION_MODAL_ALIAS, +} from "./index.js"; + +const modals: Array = [ + { + type: "modal", + alias: EDIT_DETAIL_DEFINITION_MODAL_ALIAS, + name: "Edit Detail Definition Modal", + js: () => import("./edit-detail-definition-modal.element.js"), + } +]; + +export const manifests = [...modals]; diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/authentication-status-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/authentication-status-property-editor.element.ts new file mode 100644 index 0000000..1d7d55b --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/authentication-status-property-editor.element.ts @@ -0,0 +1,62 @@ +import { + html, + customElement, + property, + state, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; + +const elementName = "personalisation-group-authentication-status-criteria-property-editor"; + +@customElement(elementName) +export class AuthenticationStatusCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + this._isAuthenticated = value.length > 0 + ? JSON.parse(value).isAuthenticated + : false; + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _isAuthenticated: boolean = false; + + #toggleIsAuthenticated() { + this._isAuthenticated = !this._isAuthenticated; + this.#refreshValue(); + } + + + #refreshValue() { + this.#value = JSON.stringify({ isAuthenticated: this._isAuthenticated}); + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the authentication status settings:

+ `; + } +} + +export default AuthenticationStatusCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: AuthenticationStatusCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/continent-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/continent-property-editor.element.ts new file mode 100644 index 0000000..79f241a --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/continent-property-editor.element.ts @@ -0,0 +1,215 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; +import { ContinentDto, GeoLocationService } from "@personalisationgroups/generated"; +import { tryExecute } from "@umbraco-cms/backoffice/resources"; + +type ContinentSetting = { + match: string; + codes: Array, + names: Array +}; + +const elementName = "personalisation-group-continent-criteria-property-editor"; + +@customElement(elementName) +export class ContinentCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: ContinentSetting = { match: "IsLocatedIn", codes: [], names: [] }; + + @state() + private _availableContinents: Array = []; + + @state() + private _selectedContinent?: ContinentDto = undefined; + + async connectedCallback() { + super.connectedCallback(); + await this.#getAvailableContinents(); + } + + async #getAvailableContinents() { + const { data } = await tryExecute(GeoLocationService.getContinentCollection()); + this._availableContinents = data || []; + this._selectedContinent = this._availableContinents.length > 0 + ? this._availableContinents[0] + : undefined; + this.#ensureValues(); + } + + #ensureValues() { + const availableCodes = this._availableContinents.map(c => c.code); + this._typedValue.codes = this._typedValue.codes.filter(c => availableCodes.indexOf(c) > -1); + this._typedValue.names = []; + for (let index = 0; index < this._typedValue.codes.length; index++) { + const code = this._typedValue.codes[index]; + this._typedValue.names.push(this.#getContinentByCode(code)!.name); + } + } + + #getMatchOptions() { + return [{ + name: "Visitor is located in", + value: "IsLocatedIn", + selected: this._typedValue.match === "IsLocatedIn", + }, + { + name: "Visitor is not located in", + value: "IsNotLocatedIn", + selected: this._typedValue.match === "IsNotLocatedIn", + }, + { + name: "Visitor cannot be located", + value: "CouldNotBeLocated", + selected: this._typedValue.match === "CouldNotBeLocated", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #getAvailableContinentOptions() { + return this._availableContinents?.map((c) => { + return { + name: c.name, + value: c.code, + selected: c.code === this._selectedContinent?.code, + } + }) ?? []; + } + + #onSelectedContinentChange(e: UUISelectEvent) { + const code = e.target.value.toString(); + this._selectedContinent = this.#getContinentByCode(code); + } + + #getContinentByCode(code: string) { + if (this._availableContinents === undefined) { + return undefined; + } + + return this._availableContinents.find(c => c.code === code); + } + + #addContinent() { + if (!this._selectedContinent) { + return; + } + + if (this._typedValue.codes.indexOf(this._selectedContinent.code) > -1) { + return; + } + + this._typedValue.codes.push(this._selectedContinent.code); + this._typedValue.names.push(this._selectedContinent.name); + this.#refreshValue(); + } + + #removeContinent(index: number) { + this._typedValue.codes.splice(index, 1); + this._typedValue.names.splice(index, 1); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the continent settings:

+ + + + + + + + + +
+ +
+ + + + ${this._typedValue.codes.map((c, i) => html` + + + + `)} +
${this.#getContinentByCode(c)?.name} + this.#removeContinent(i)} + > +
+
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + + table.selected-items td { + padding: 4px; + } + `, + ]; +} + +export default ContinentCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: ContinentCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/cookie-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/cookie-property-editor.element.ts new file mode 100644 index 0000000..d274d1d --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/cookie-property-editor.element.ts @@ -0,0 +1,178 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIInputEvent, UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; + +type CookieSetting = { + key: string; + match: string; + value: string +}; + +const elementName = "personalisation-group-cookie-criteria-property-editor"; + +@customElement(elementName) +export class CookieCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: CookieSetting = { key: "", match: "Exists", value: "" }; + + #onKeyChange(e: UUIInputEvent) { + this._typedValue.key = e.target.value.toString(); + this.#refreshValue(); + } + + #getMatchOptions() { + return [{ + name: "Cookie is present", + value: "Exists", + selected: this._typedValue.match === "Exists", + }, + { + name: "Cookie is absent", + value: "DoesNotExist", + selected: this._typedValue.match === "DoesNotExist", + }, + { + name: "Cookie value matches", + value: "MatchesValue", + selected: this._typedValue.match === "MatchesValue", + }, + { + name: "Cookie value contains", + value: "ContainsValue", + selected: this._typedValue.match === "ContainsValue", + }, + { + name: "Cookie value is greater than", + value: "GreaterThanValue", + selected: this._typedValue.match === "GreaterThanValue", + }, + { + name: "Cookie value is greater than or equal to", + value: "GreaterThanOrEqualToValue", + selected: this._typedValue.match === "GreaterThanOrEqualToValue", + }, + { + name: "Cookie value is less than", + value: "LessThanValue", + selected: this._typedValue.match === "LessThanValue", + }, + { + name: "Cookie value is less than or equal to", + value: "LessThanOrEqualToValue", + selected: this._typedValue.match === "LessThanOrEqualToValue", + }, + { + name: "Matches regular expression", + value: "MatchesRegex", + selected: this._typedValue.match === "MatchesRegex", + }, + { + name: "Does not match regular expression", + value: "DoesNotMatchRegex", + selected: this._typedValue.match === "DoesNotMatchRegex", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #isValueRequired() { + return this._typedValue.match !== "Exists" && this._typedValue.match !== "DoesNotExist" + } + + #onValueChange(e: UUIInputEvent) { + this._typedValue.value = e.target.value.toString(); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the cookie settings:

+ + + + + + + + + + + + + +
+ + +
+ +
+ + +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default CookieCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: CookieCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/country-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/country-property-editor.element.ts new file mode 100644 index 0000000..0012a0b --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/country-property-editor.element.ts @@ -0,0 +1,215 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; +import { CountryDto, GeoLocationService } from "@personalisationgroups/generated"; +import { tryExecute } from "@umbraco-cms/backoffice/resources"; + +type CountrySetting = { + match: string; + codes: Array, + names: Array +}; + +const elementName = "personalisation-group-country-criteria-property-editor"; + +@customElement(elementName) +export class CountryCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: CountrySetting = { match: "IsLocatedIn", codes: [], names: [] }; + + @state() + private _availableCountries: Array = []; + + @state() + private _selectedCountry?: CountryDto = undefined; + + async connectedCallback() { + super.connectedCallback(); + await this.#getAvailableCountries(); + } + + async #getAvailableCountries() { + const { data } = await tryExecute(GeoLocationService.getCountryCollection()); + this._availableCountries = data || []; + this._selectedCountry = this._availableCountries.length > 0 + ? this._availableCountries[0] + : undefined; + this.#ensureValues(); + } + + #ensureValues() { + const availableCodes = this._availableCountries.map(c => c.code); + this._typedValue.codes = this._typedValue.codes.filter(c => availableCodes.indexOf(c) > -1); + this._typedValue.names = []; + for (let index = 0; index < this._typedValue.codes.length; index++) { + const code = this._typedValue.codes[index]; + this._typedValue.names.push(this.#getCountryByCode(code)!.name); + } + } + + #getMatchOptions() { + return [{ + name: "Visitor is located in", + value: "IsLocatedIn", + selected: this._typedValue.match === "IsLocatedIn", + }, + { + name: "Visitor is not located in", + value: "IsNotLocatedIn", + selected: this._typedValue.match === "IsNotLocatedIn", + }, + { + name: "Visitor cannot be located", + value: "CouldNotBeLocated", + selected: this._typedValue.match === "CouldNotBeLocated", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #getAvailableCountryOptions() { + return this._availableCountries?.map((c) => { + return { + name: c.name, + value: c.code, + selected: c.code === this._selectedCountry?.code, + } + }) ?? []; + } + + #onSelectedCountryChange(e: UUISelectEvent) { + const code = e.target.value.toString(); + this._selectedCountry = this.#getCountryByCode(code); + } + + #getCountryByCode(code: string) { + if (this._availableCountries === undefined) { + return undefined; + } + + return this._availableCountries.find(c => c.code === code); + } + + #addCountry() { + if (!this._selectedCountry) { + return; + } + + if (this._typedValue.codes.indexOf(this._selectedCountry.code) > -1) { + return; + } + + this._typedValue.codes.push(this._selectedCountry.code); + this._typedValue.names.push(this._selectedCountry.name); + this.#refreshValue(); + } + + #removeCountry(index: number) { + this._typedValue.codes.splice(index, 1); + this._typedValue.names.splice(index, 1); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the country settings:

+ + + + + + + + + +
+ +
+ + + + ${this._typedValue.codes.map((c, i) => html` + + + + `)} +
${this.#getCountryByCode(c)?.name} + this.#removeCountry(i)} + > +
+
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + + table.selected-items td { + padding: 4px; + } + `, + ]; +} + +export default CountryCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: CountryCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/day-of-week-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/day-of-week-property-editor.element.ts new file mode 100644 index 0000000..981ee1d --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/day-of-week-property-editor.element.ts @@ -0,0 +1,99 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIBooleanInputEvent } from "@umbraco-cms/backoffice/external/uui"; + +const elementName = "personalisation-group-day-of-week-criteria-property-editor"; + +@customElement(elementName) +export class DayOfWeekCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._dayValues = JSON.parse(value); + } else { + this._dayValues = []; + } + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _dayValues: Array = []; + + #days: Array = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; + + #isDaySelected(index: number) { + const dayNumber = index + 1; + return this._dayValues.indexOf(dayNumber) > -1; + } + + #onDayToggle(e: UUIBooleanInputEvent, index: number) { + const dayNumber = index + 1; + const checked = e.target.checked; + if (checked) { + this._dayValues.push(dayNumber); + this._dayValues.sort(); + } else { + this._dayValues = this._dayValues.filter(d => d !== dayNumber) + } + + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._dayValues); + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please select the days for which this group will be valid:

+ + ${this.#days.map((d, i) => + html` + + + + `)} +
${d}: + + this.#onDayToggle(e, i)} + > +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default DayOfWeekCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: DayOfWeekCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/host-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/host-property-editor.element.ts new file mode 100644 index 0000000..6bf91ed --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/host-property-editor.element.ts @@ -0,0 +1,126 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIInputEvent, UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; + +type HostSetting = { + match: string; + value: string +}; + +const elementName = "personalisation-group-host-criteria-property-editor"; + +@customElement(elementName) +export class HostCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: HostSetting = { match: "MatchesValue", value: "" }; + + #getMatchOptions() { + return [{ + name: "Matches value", + value: "MatchesValue", + selected: this._typedValue.match === "MatchesValue", + }, + { + name: "Does not match value", + value: "DoesNotMatchValue", + selected: this._typedValue.match === "DoesNotMatchValue", + }, + { + name: "Contains value", + value: "ContainsValue", + selected: this._typedValue.match === "ContainsValue", + }, + { + name: "Does not contain value", + value: "DoesNotContainValue", + selected: this._typedValue.match === "DoesNotContainValue", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #onValueChange(e: UUIInputEvent) { + this._typedValue.value = e.target.value.toString(); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the host settings:

+ + + + + + + + + +
+ +
+ + +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default HostCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: HostCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/manifests.ts b/PersonalisationGroups/Client/src/property-editor/criteria/manifests.ts new file mode 100644 index 0000000..8b9f504 --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/manifests.ts @@ -0,0 +1,192 @@ +import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.authenticationStatusCriteria', + name: 'Personalisation Group Definition Editor For Authentication Status Criteria', + js: () => import('./authentication-status-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Authentication Status Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.continentCriteria', + name: 'Personalisation Group Definition Editor For Continent Criteria', + js: () => import('./continent-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Continent Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.cookieCriteria', + name: 'Personalisation Group Definition Editor For Cookie Criteria', + js: () => import('./cookie-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Cookie Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.countryCriteria', + name: 'Personalisation Group Definition Editor For Country Criteria', + js: () => import('./country-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Country Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.dayOfWeekCriteria', + name: 'Personalisation Group Definition Editor For Day Of Week Criteria', + js: () => import('./day-of-week-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Day Of Week Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.hostCriteria', + name: 'Personalisation Group Definition Editor For Host Criteria', + js: () => import('./host-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Host Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.memberGroupCriteria', + name: 'Personalisation Group Definition Editor For Member Group Criteria', + js: () => import('./member-group-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Member Group Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.memberProfileFieldCriteria', + name: 'Personalisation Group Definition Editor For Member Profile Field Criteria', + js: () => import('./member-profile-field-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Member Profile Field Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.memberTypeCriteria', + name: 'Personalisation Group Definition Editor For Member Type Criteria', + js: () => import('./member-type-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Member Type Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.monthOfYearCriteria', + name: 'Personalisation Group Definition Editor For Month Of Year Criteria', + js: () => import('./month-of-year-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Month Of Year Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.numberOfVisitsCriteria', + name: 'Personalisation Group Definition Editor For Number Of Visits Criteria', + js: () => import('./number-of-visits-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Number Of Visits Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.pagesViewedCriteria', + name: 'Personalisation Group Definition Editor For Pages Viewed Criteria', + js: () => import('./pages-viewed-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Pages Viewed Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.querystringCriteria', + name: 'Personalisation Group Definition Editor For QueryString Criteria', + js: () => import('./querystring-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For QueryString Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.referralCriteria', + name: 'Personalisation Group Definition Editor For Referral Criteria', + js: () => import('./referral-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Referral Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.regionCriteria', + name: 'Personalisation Group Definition Editor For Region Criteria', + js: () => import('./region-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Region Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.sessionCriteria', + name: 'Personalisation Group Definition Editor For Session Criteria', + js: () => import('./session-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Session Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.timeOfDayCriteria', + name: 'Personalisation Group Definition Editor For Time Of Day Criteria', + js: () => import('./time-of-day-property-editor.element.js'), + meta: { + label: 'Personalisation Group Definition Editor For Time Of Day Criteria', + icon: 'icon-settings', + group: 'personalisation' + }, + }, + +]; diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/member-group-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/member-group-property-editor.element.ts new file mode 100644 index 0000000..07f2465 --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/member-group-property-editor.element.ts @@ -0,0 +1,141 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; +import { tryExecute } from "@umbraco-cms/backoffice/resources"; +import { MemberGroupDto, MemberService } from "@personalisationgroups/generated"; + +type MemberGroupSetting = { + match: string; + groupName: string +}; + +const elementName = "personalisation-group-member-group-criteria-property-editor"; + +@customElement(elementName) +export class MemberGroupCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: MemberGroupSetting = { match: "IsInGroup", groupName: "" }; + + @state() + private _memberGroups: Array = []; + + async connectedCallback() { + super.connectedCallback(); + await this.#getMemberGroups(); + } + + async #getMemberGroups() { + const { data } = await tryExecute(MemberService.getMemberGroupCollection()); + this._memberGroups = data || []; + } + + #getMatchOptions() { + return [{ + name: "Is in group", + value: "IsInGroup", + selected: this._typedValue.match === "IsInGroup", + }, + { + name: "Is not in group", + value: "IsNotInGroup", + selected: this._typedValue.match === "IsNotInGroup", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #getGroupOptions() { + return this._memberGroups.map((g) => { + return { + name: g.name, + value: g.name, + selected: this._typedValue.groupName === g.name + } + }); + } + + #onGroupChange(e: UUISelectEvent) { + this._typedValue.groupName = e.target.value.toString(); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the member group settings:

+ + + + + + + + + +
+ +
+ +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default MemberGroupCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: MemberGroupCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/member-profile-field-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/member-profile-field-property-editor.element.ts new file mode 100644 index 0000000..80fb169 --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/member-profile-field-property-editor.element.ts @@ -0,0 +1,180 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIInputEvent, UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; +import { tryExecute } from "@umbraco-cms/backoffice/resources"; +import { MemberProfileFieldDto, MemberService } from "@personalisationgroups/generated"; + +type MemberProfileFieldSetting = { + alias: string; + match: string; + value: string; +}; + +const elementName = "personalisation-group-member-profile-field-criteria-property-editor"; + +@customElement(elementName) +export class MemberProfileFieldCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: MemberProfileFieldSetting = { alias: "", match: "Matches", value: "" }; + + @state() + private _memberProfileFields: Array = []; + + async connectedCallback() { + super.connectedCallback(); + await this.#getMemberProfileFields(); + } + + async #getMemberProfileFields() { + const { data } = await tryExecute(MemberService.getMemberProfileFieldCollection()); + this._memberProfileFields = data || []; + } + + #getFieldOptions() { + return this._memberProfileFields.map((f) => { + return { + name: f.name, + value: f.alias, + selected: this._typedValue.alias === f.alias + } + }); + } + + #onFieldChange(e: UUISelectEvent) { + this._typedValue.alias = e.target.value.toString(); + this.#refreshValue(); + } + + #getMatchOptions() { + return [{ + name: "Matches", + value: "MatchesValue", + selected: this._typedValue.match === "MatchesValue", + }, + { + name: "Does not match", + value: "DoesNotMatchValue", + selected: this._typedValue.match === "DoesNotMatchValue", + }, + { + name: "Is greater than", + value: "GreaterThanValue", + selected: this._typedValue.match === "GreaterThanValue", + }, + { + name: "Is greater than or equal to", + value: "GreaterThanOrEqualToValue", + selected: this._typedValue.match === "GreaterThanOrEqualToValue", + }, + { + name: "Is less than", + value: "LessThanValue", + selected: this._typedValue.match === "LessThanValue", + }, + { + name: "Is less than or equal to", + value: "LessThanOrEqualToValue", + selected: this._typedValue.match === "LessThanOrEqualToValue", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #onValueChange(e: UUIInputEvent) { + this._typedValue.value = e.target.value.toString(); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the member profile field settings:

+ + + + + + + + + + + + + + + +
+ +
+ +
+ + +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default MemberProfileFieldCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: MemberProfileFieldCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/member-type-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/member-type-property-editor.element.ts new file mode 100644 index 0000000..fc88e60 --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/member-type-property-editor.element.ts @@ -0,0 +1,141 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; +import { tryExecute } from "@umbraco-cms/backoffice/resources"; +import { MemberTypeDto, MemberService } from "@personalisationgroups/generated"; + +type MemberTypeSetting = { + match: string; + typeName: string +}; + +const elementName = "personalisation-group-member-type-criteria-property-editor"; + +@customElement(elementName) +export class MemberTypeCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: MemberTypeSetting = { match: "IsOfType", typeName: "" }; + + @state() + private _memberTypes: Array = []; + + async connectedCallback() { + super.connectedCallback(); + await this.#getMemberTypes(); + } + + async #getMemberTypes() { + const { data } = await tryExecute(MemberService.getMemberTypeCollection()); + this._memberTypes = data || []; + } + + #getMatchOptions() { + return [{ + name: "Is of type", + value: "IfOfType", + selected: this._typedValue.match === "IsOfType", + }, + { + name: "Is not of type", + value: "IsNotOfType", + selected: this._typedValue.match === "IsNotOfType", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #getTypeOptions() { + return this._memberTypes.map((t) => { + return { + name: t.name, + value: t.alias, + selected: this._typedValue.typeName === t.alias + } + }); + } + + #onTypeChange(e: UUISelectEvent) { + this._typedValue.typeName = e.target.value.toString(); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the member type settings:

+ + + + + + + + + +
+ +
+ +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default MemberTypeCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: MemberTypeCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/month-of-year-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/month-of-year-property-editor.element.ts new file mode 100644 index 0000000..e3706e1 --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/month-of-year-property-editor.element.ts @@ -0,0 +1,99 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIBooleanInputEvent } from "@umbraco-cms/backoffice/external/uui"; + +const elementName = "personalisation-group-months-of-year-criteria-property-editor"; + +@customElement(elementName) +export class MonthsOfYearCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._monthValues = JSON.parse(value); + } else { + this._monthValues = []; + } + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _monthValues: Array = []; + + #months: Array = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; + + #isMonthSelected(index: number) { + const monthNumber = index + 1; + return this._monthValues.indexOf(monthNumber) > -1; + } + + #onMonthToggle(e: UUIBooleanInputEvent, index: number) { + const monthNumber = index + 1; + const checked = e.target.checked; + if (checked) { + this._monthValues.push(monthNumber); + this._monthValues.sort(); + } else { + this._monthValues = this._monthValues.filter(d => d !== monthNumber) + } + + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._monthValues); + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please select the months for which this group will be valid:

+ + ${this.#months.map((d, i) => + html` + + + + `)} +
${d}: + + this.#onMonthToggle(e, i)} + > +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default MonthsOfYearCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: MonthsOfYearCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/number-of-visits-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/number-of-visits-property-editor.element.ts new file mode 100644 index 0000000..0c55fb4 --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/number-of-visits-property-editor.element.ts @@ -0,0 +1,124 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIInputEvent, UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; + +type NumberOfVisitsSetting = { + match: string; + number: number +}; + +const elementName = "personalisation-group-number-of-visits-criteria-property-editor"; + +@customElement(elementName) +export class NumberOfVisitsCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: NumberOfVisitsSetting = { match: "MoreThan", number: 0 }; + + #getMatchOptions() { + return [{ + name: "More than", + value: "MoreThan", + selected: this._typedValue.match === "MoreThan", + }, + { + name: "Less than", + value: "LessThan", + selected: this._typedValue.match === "LessThan", + }, + { + name: "Exactly", + value: "Exactly", + selected: this._typedValue.match === "Exactly", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #onNumberChange(e: UUIInputEvent) { + this._typedValue.number = parseInt(e.target.value.toString()); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the number of visits settings:

+ + + + + + + + + +
+ +
+ + +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default NumberOfVisitsCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: NumberOfVisitsCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/pages-viewed-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/pages-viewed-property-editor.element.ts new file mode 100644 index 0000000..618525f --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/pages-viewed-property-editor.element.ts @@ -0,0 +1,182 @@ +import { + html, + customElement, + property, + state, + repeat, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; +import { UmbDocumentItemModel, UmbDocumentItemRepository, UmbDocumentPickerContext } from "@umbraco-cms/backoffice/document"; + +const elementName = "personalisation-group-pages-viewed-criteria-property-editor"; + +type PagesViewedSetting = { + match: string; + nodeKeys: Array +}; + +@customElement(elementName) +export class PagesViewedCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + #itemRepository = new UmbDocumentItemRepository(this); + + #pickerContext = new UmbDocumentPickerContext(this); + + @state() + private _typedValue: PagesViewedSetting = { match: "ViewedAny", nodeKeys: [] }; + + @state() + private _pickedItems: Array = []; + + constructor() { + super(); + this.observe(this.#pickerContext.selection, async (selection) => (await this.#refreshPickedPages(selection)), '_observeSelection'); + } + + protected async firstUpdated(): Promise { + this._pickedItems = await this.#getPickedPages(this._typedValue.nodeKeys); + } + + async #getPickedPages(ids: Array) { + if (ids.length === 0) return []; + const { data } = await this.#itemRepository.requestItems(ids); + return data || []; + } + + #getMatchOptions() { + return [{ + name: "Viewed any one of the following pages", + value: "ViewedAny", + selected: this._typedValue.match === "ViewedAny", + }, + { + name: "Viewed all one of the following pages", + value: "ViewedAll", + selected: this._typedValue.match === "ViewedAll", + }, + { + name: "Not viewed any one of the following pages", + value: "NotViewedAny", + selected: this._typedValue.match === "NotViewedAny", + }, + { + name: "Not viewed all one of the following pages", + value: "NotViewedAll", + selected: this._typedValue.match === "NotViewedAll", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #removePickedPage(id: string) { + this._typedValue.nodeKeys = this._typedValue.nodeKeys.filter(i => i !== id); + this._pickedItems = this._pickedItems.filter(i => i.unique !== id); + this.#refreshValue(); + } + + #openPicker() { + this.#pickerContext.openPicker({ + hideTreeRoot: true + }); + } + + async #refreshPickedPages(ids: Array) { + this._typedValue.nodeKeys = ids; + this._pickedItems = await this.#getPickedPages(ids); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the pages viewed criteria settings:

+ + + + + + + + + +
+ +
+ + ${repeat( + this._pickedItems, + (item) => item.unique, + (item) => this.#renderItem(item), + )} + + +
`; + } + + #renderItem(item: UmbDocumentItemModel) { + return html` + + + + this.#removePickedPage(item.unique)} + label="Remove"> + Remove + + + ` + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default PagesViewedCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: PagesViewedCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/querystring-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/querystring-property-editor.element.ts new file mode 100644 index 0000000..b1e93cd --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/querystring-property-editor.element.ts @@ -0,0 +1,153 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIInputEvent, UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; + +type QueryStringSetting = { + key: string; + match: string; + value: string +} + +const elementName = "personalisation-group-querystring-criteria-property-editor"; + +@customElement(elementName) +export class QueryStringCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: QueryStringSetting = { key: "", match: "Exists", value: "" }; + + #onKeyChange(e: UUIInputEvent) { + this._typedValue.key = e.target.value.toString(); + this.#refreshValue(); + } + + #getMatchOptions() { + return [{ + name: "Matches value", + value: "MatchesValue", + selected: this._typedValue.match === "MatchesValue", + }, + { + name: "Does not match value", + value: "DoesNotMatchValue", + selected: this._typedValue.match === "DoesNotMatchValue", + }, + { + name: "Contains value", + value: "ContainsValue", + selected: this._typedValue.match === "ContainsValue", + }, + { + name: "Does not contain value", + value: "DoesNotContainValue", + selected: this._typedValue.match === "DoesNotContainValue", + }, + { + name: "Matches regular expression", + value: "MatchesRegex", + selected: this._typedValue.match === "MatchesRegex", + }, + { + name: "Does not match regular expression", + value: "DoesNotMatchRegex", + selected: this._typedValue.match === "DoesNotMatchRegex", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #onValueChange(e: UUIInputEvent) { + this._typedValue.value = e.target.value.toString(); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the cookie settings:

+ + + + + + + + + + + + + +
+ + +
+ +
+ + +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default QueryStringCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: QueryStringCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/referral-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/referral-property-editor.element.ts new file mode 100644 index 0000000..ab1cc26 --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/referral-property-editor.element.ts @@ -0,0 +1,126 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIInputEvent, UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; + +type ReferralSetting = { + match: string; + value: string +}; + +const elementName = "personalisation-group-referral-criteria-property-editor"; + +@customElement(elementName) +export class ReferralCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: ReferralSetting = { match: "MatchesValue", value: "" }; + + #getMatchOptions() { + return [{ + name: "Matches value", + value: "MatchesValue", + selected: this._typedValue.match === "MatchesValue", + }, + { + name: "Does not match value", + value: "DoesNotMatchValue", + selected: this._typedValue.match === "DoesNotMatchValue", + }, + { + name: "Contains value", + value: "ContainsValue", + selected: this._typedValue.match === "ContainsValue", + }, + { + name: "Does not contain value", + value: "DoesNotContainValue", + selected: this._typedValue.match === "DoesNotContainValue", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #onValueChange(e: UUIInputEvent) { + this._typedValue.value = e.target.value.toString(); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the host settings:

+ + + + + + + + + +
+ +
+ + +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default ReferralCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: ReferralCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/region-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/region-property-editor.element.ts new file mode 100644 index 0000000..7698205 --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/region-property-editor.element.ts @@ -0,0 +1,243 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; +import { CountryDto, RegionDto, GeoLocationService } from "@personalisationgroups/generated"; +import { tryExecute } from "@umbraco-cms/backoffice/resources"; + +type RegionSetting = { + match: string; + countryCode: string, + codes: Array, + names: Array +}; + +const elementName = "personalisation-group-region-criteria-property-editor"; + +@customElement(elementName) +export class RegionCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: RegionSetting = { match: "IsLocatedIn", countryCode: "", codes: [], names: [] }; + + @state() + private _countries: Array = []; + + @state() + private _availableRegions: Array = []; + + @state() + private _selectedRegion?: RegionDto = undefined; + + async connectedCallback() { + super.connectedCallback(); + await this.#getCountries(); + } + + async #getCountries() { + const { data } = await tryExecute(GeoLocationService.getCountryCollection()); + this._countries = data || []; + if (data && data.length > 0) { + this._availableRegions = await this.#getAvailableRegions(data[0].code); + } else { + this._availableRegions = []; + } + } + + async #getAvailableRegions(countryCode: string) { + const { data } = await tryExecute(GeoLocationService.getRegionCollection({ countryCode })); + return data || []; + } + + #getMatchOptions() { + return [{ + name: "Visitor is located in", + value: "IsLocatedIn", + selected: this._typedValue.match === "IsLocatedIn", + }, + { + name: "Visitor is not located in", + value: "IsNotLocatedIn", + selected: this._typedValue.match === "IsNotLocatedIn", + }, + { + name: "Visitor cannot be located", + value: "CouldNotBeLocated", + selected: this._typedValue.match === "CouldNotBeLocated", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #getCountryOptions() { + return this._countries?.map((c) => { + return { + name: c.name, + value: c.code, + selected: c.code === this._typedValue.countryCode, + } + }) ?? []; + } + + async #onCountryChange(e: UUISelectEvent) { + const countryCode = e.target.value.toString(); + this._typedValue.countryCode = countryCode; + this._availableRegions = await this.#getAvailableRegions(countryCode); + this.#refreshValue(); + } + + #getAvailableRegionOptions() { + return this._availableRegions?.map((c) => { + return { + name: c.name, + value: c.code, + selected: c.code === this._selectedRegion?.code, + } + }) ?? []; + } + + #onSelectedRegionChange(e: UUISelectEvent) { + const code = e.target.value.toString(); + this._selectedRegion = this.#getRegionByCode(code); + } + + #getRegionByCode(code: string) { + if (this._availableRegions === undefined) { + return undefined; + } + + return this._availableRegions.find(r => r.code === code); + } + + #addRegion() { + if (!this._selectedRegion) { + return; + } + + if (this._typedValue.codes.indexOf(this._selectedRegion.code) > -1) { + return; + } + + this._typedValue.codes.push(this._selectedRegion.code); + this._typedValue.names.push(this._selectedRegion.name); + this.#refreshValue(); + } + + #removeRegion(index: number) { + this._typedValue.codes.splice(index, 1); + this._typedValue.names.splice(index, 1); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the region settings:

+ + + + + + + + + + + + + +
+ +
+ +
+ + + + ${this._typedValue.names.map((r, i) => html` + + + + `)} +
${r} + this.#removeRegion(i)} + > +
+
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + + table.selected-items td { + padding: 4px; + } + `, + ]; +} + +export default RegionCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: RegionCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/session-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/session-property-editor.element.ts new file mode 100644 index 0000000..8b7b23d --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/session-property-editor.element.ts @@ -0,0 +1,178 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIInputEvent, UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; + +type SessionSetting = { + key: string; + match: string; + value: string +}; + +const elementName = "personalisation-group-session-criteria-property-editor"; + +@customElement(elementName) +export class SessionCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: SessionSetting = { key: "", match: "Exists", value: "" }; + + #onKeyChange(e: UUIInputEvent) { + this._typedValue.key = e.target.value.toString(); + this.#refreshValue(); + } + + #getMatchOptions() { + return [{ + name: "Session key is present", + value: "Exists", + selected: this._typedValue.match === "Exists", + }, + { + name: "Session key is absent", + value: "DoesNotExist", + selected: this._typedValue.match === "DoesNotExist", + }, + { + name: "Session value matches", + value: "MatchesValue", + selected: this._typedValue.match === "MatchesValue", + }, + { + name: "Session value contains", + value: "ContainsValue", + selected: this._typedValue.match === "ContainsValue", + }, + { + name: "Session value is greater than", + value: "GreaterThanValue", + selected: this._typedValue.match === "GreaterThanValue", + }, + { + name: "Session value is greater than or equal to", + value: "GreaterThanOrEqualToValue", + selected: this._typedValue.match === "GreaterThanOrEqualToValue", + }, + { + name: "Session value is less than", + value: "LessThanValue", + selected: this._typedValue.match === "LessThanValue", + }, + { + name: "Session value is less than or equal to", + value: "LessThanOrEqualToValue", + selected: this._typedValue.match === "LessThanOrEqualToValue", + }, + { + name: "Matches regular expression", + value: "MatchesRegex", + selected: this._typedValue.match === "MatchesRegex", + }, + { + name: "Does not match regular expression", + value: "DoesNotMatchRegex", + selected: this._typedValue.match === "DoesNotMatchRegex", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this._typedValue.match = e.target.value.toString(); + this.#refreshValue(); + } + + #isValueRequired() { + return this._typedValue.match !== "Exists" && this._typedValue.match !== "DoesNotExist" + } + + #onValueChange(e: UUIInputEvent) { + this._typedValue.value = e.target.value.toString(); + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue) + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the session settings:

+ + + + + + + + + + + + + +
+ + +
+ +
+ + +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default SessionCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: SessionCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/criteria/time-of-day-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/criteria/time-of-day-property-editor.element.ts new file mode 100644 index 0000000..149f49c --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/criteria/time-of-day-property-editor.element.ts @@ -0,0 +1,188 @@ +import { + html, + customElement, + property, + state, + css, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIInputElement, UUIInputEvent } from "@umbraco-cms/backoffice/external/uui"; + +type TimeOfDaySetting = { + from: number; + to: number; +}; + +const elementName = "personalisation-group-time-of-day-criteria-property-editor"; + +@customElement(elementName) +export class TimeOfDayCriteriaPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + #value: string = ""; + @property({ type: String }) + set value(value: string) { + this.#value = value; + if (value.length > 0) { + this._typedValue = JSON.parse(value); + } + + this.requestUpdate(); + } + + get value() { + return this.#value; + } + + @state() + private _typedValue: Array = []; + + @state() + private _editIndex: number = -1; + + #onFromChange(e: UUIInputEvent) { + this._typedValue[this._editIndex].from = parseInt(e.target.value.toString()); + this.#refreshValue(); + } + + #onToChange(e: UUIInputEvent) { + this._typedValue[this._editIndex].to = parseInt(e.target.value.toString()); + this.#refreshValue(); + } + + #editEntry(index: number) { + this._editIndex = index; + } + + #removeEntry(index: number) { + this._typedValue.splice(index, 1); + this.#refreshValue(); + } + + #closeEntry() { + this._editIndex = -1; + } + + #addEntry() { + const fromElement = this.shadowRoot?.getElementById("newFrom") as UUIInputElement; + const toElement = this.shadowRoot?.getElementById("newTo") as UUIInputElement; + this._typedValue.push({ + from: parseInt(fromElement.value.toString()), + to: parseInt(toElement.value.toString()), + }); + fromElement.value = ""; + toElement.value = ""; + this.#refreshValue(); + } + + #refreshValue() { + this.#value = JSON.stringify(this._typedValue); + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + render() { + return html` +

Please enter the time ranges for which this group will be valid. Times must be entered in 24h clock format, e.g. 1030, 1400:

+ + + + + + + + + + ${this._typedValue.map((t, i) => + html` + + + + + `)} + + + + + + + + +
FromTo
+ ${this._editIndex == i + ? html` + + ` + : html` + ${t.from.toString().padStart(4, "0")}`} + + ${this._editIndex == i + ? html` + + ` + : html` + ${t.to.toString().padStart(4, "0")}`} + + ${this._editIndex == i + ? html` + ` + : html` + this.#editEntry(i)} + > + this.#removeEntry(i)} + >`} +
+ + + + + +
`; + } + + static styles = [ + css` + td.label { + vertical-align: top; + padding-top: 4px; + padding-right: 4px; + } + `, + ]; +} + +export default TimeOfDayCriteriaPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: TimeOfDayCriteriaPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/group-definition/group-definition-input.element.ts b/PersonalisationGroups/Client/src/property-editor/group-definition/group-definition-input.element.ts new file mode 100644 index 0000000..60c0f4b --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/group-definition/group-definition-input.element.ts @@ -0,0 +1,360 @@ +import { css, customElement, html, property, state, until, when } from "@umbraco-cms/backoffice/external/lit"; +import { PersonalisationGroup, PersonalisationGroupDetail } from "../../types"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { UUIInputEvent, UUISelectEvent } from "@umbraco-cms/backoffice/external/uui"; +import { tryExecute } from "@umbraco-cms/backoffice/resources"; +import { CriteriaDto, CriteriaService } from "@personalisationgroups/generated"; +import { umbExtensionsRegistry } from "@umbraco-cms/backoffice/extension-registry"; +import { ManifestPersonalisationGroupDefinitionDetailTranslator, PersonalisationGroupDefinitionDetailTranslatorApi } from "../../translator/translator.interface"; +import { loadManifestApi } from "@umbraco-cms/backoffice/extension-api"; +import { UMB_MODAL_MANAGER_CONTEXT, umbConfirmModal } from "@umbraco-cms/backoffice/modal"; +import { EDIT_DETAIL_DEFINITION_MODAL, EditDetailDefinitionModalValue } from "../../modal"; + +const elementName = "personalisation-group-definition-input"; + +@customElement(elementName) +export class PersonalisationGroupDefinitionInput extends UmbLitElement { + + @property({ attribute: false }) + set value(data: PersonalisationGroup) { + // Need to create a new object here, as the incoming one is immutable. + this.#value = { + match: data.match ?? "All", + duration: data.duration ?? "Page", + score: data.score ?? 50, + details: data.details ? [...data.details] : [] + }; + } + + get value() { + return this.#value; + } + + #value: PersonalisationGroup = { match: "All", duration: "Page", score: 50, details: [] }; + + #modalContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; + + @state() + private _availableCriteria: Array = []; + + @state() + private _selectedCriteria?: CriteriaDto = undefined; + + #translators: Array = []; + + constructor() { + super(); + + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { + this.#modalContext = instance; + }); + + this.#translators = umbExtensionsRegistry.getAllExtensions() + .filter(e => e.type === "PersonalisationGroupDetailDefinitionTranslator") + .map(mt => mt as ManifestPersonalisationGroupDefinitionDetailTranslator); + } + + async connectedCallback() { + super.connectedCallback(); + await this.#getAvailableCriteria(); + } + + async #getAvailableCriteria() { + const { data } = await tryExecute(CriteriaService.getCriteriaCollection()); + this._availableCriteria = data || []; + this._selectedCriteria = this._availableCriteria.length > 0 + ? this._availableCriteria[0] + : undefined; + } + + #getCriteriaName(alias: string) { + var criteria = this.#getCriteriaByAlias(alias); + if (criteria) { + return criteria.name; + } + + return ""; + } + + #getCriteriaByAlias(alias: string) { + if (this._availableCriteria === undefined) { + return undefined; + } + + return this._availableCriteria.find(c => c.alias === alias); + } + + #getMatchOptions() { + return [{ + name: "All", + value: "All", + selected: this.#value.match === "All", + }, + { + name: "Any", + value: "Any", + selected: this.#value.match === "Any", + }]; + } + + #onMatchChange(e: UUISelectEvent) { + this.#value.match = e.target.value.toString(); + this.#dispatchChangeEvent(); + } + + #getDurationOptions() { + return [{ + name: "Per page request", + value: "Page", + selected: this.#value.duration === "Page", + }, + { + name: "Per session", + value: "Session", + selected: this.#value.duration === "Session", + }, + { + name: "Per visitor", + value: "Visitor", + selected: this.#value.duration === "Visitor", + }]; + } + + #onDurationChange(e: UUISelectEvent) { + this.#value.duration = e.target.value.toString(); + this.#dispatchChangeEvent(); + } + + #onScoreChange(e: UUIInputEvent) { + this.#value.score = parseInt(e.target.value.toString()); + this.#dispatchChangeEvent(); + } + + #getAvailableCriteriaOptions() { + return this._availableCriteria?.map((c) => { + return { + name: c.name, + value: c.alias, + selected: c.alias === this._selectedCriteria?.alias, + } + }) ?? []; + } + + #onSelectedCriteriaChange(e: UUISelectEvent) { + const alias = e.target.value.toString(); + this._selectedCriteria = this.#getCriteriaByAlias(alias); + } + + #dispatchChangeEvent() { + this.requestUpdate(); + this.dispatchEvent( + new CustomEvent("change", { composed: true, bubbles: true }) + ); + } + + #addCriteria() { + if (!this._selectedCriteria) { + return; + } + + const newGroupDetail = ({ alias: this._selectedCriteria?.alias, definition: "" }); + this.#value.details.push(newGroupDetail); + this.#dispatchChangeEvent(); + + this.#editCriteria(this.#value.details.length - 1); + } + + #editCriteria(index: number) { + if (!this.#modalContext) { + return; + } + + const detail = this.#value.details[index]; + const criteria = this.#getCriteriaByAlias(detail.alias); + if (!criteria) { + throw new Error("Could not find criteria with alias: " + detail.alias); + } + + const modalHandler = this.#modalContext.open( + this, + EDIT_DETAIL_DEFINITION_MODAL, + { + data: { + criteriaName: criteria.name, + propertyEditorUiAlias: `PersonalisationGroups.PropertyEditorUi.${criteria.alias}Criteria` + }, + value: { + detail + }, + } + ); + + modalHandler.onSubmit() + .then((v: EditDetailDefinitionModalValue) => { + this.#value.details[index] = v.detail; + this.#dispatchChangeEvent(); + }) + .catch(() => undefined); + } + + async #removeCriteria(index: number) { + await umbConfirmModal(this, { + headline: "Remove criteria", + content: "Are you sure you wish to remove the selected criteria?", + confirmLabel: this.localize.term("general_yes"), + color: "danger", + }); + this.#value.details.splice(index, 1); + this.#dispatchChangeEvent(); + } + + async #translateDetailDefinition(detail: PersonalisationGroupDetail) { + const translator = this.#translators.find(t => t.criteriaAlias === detail.alias); + if (!translator) { + return ""; + } + + const api = await loadManifestApi(translator.api); + if (api) { + const apiInstance = new api() as PersonalisationGroupDefinitionDetailTranslatorApi; + if (apiInstance) { + return apiInstance.translate(detail.definition); + } + } + + return ""; + } + + render() { + return html`
+ + + + + + + + + + + + + + + + + + +
+ +
+ +
+ Determines for how long a user that is matched to a personalisation group remains in it +
+
+ +
+ A number between 1 and 100, can be used to weight groups when scoring the visitor's match to a piece of content +
+
+ + + ${when(this._selectedCriteria, + () => html` +
+ ${this._selectedCriteria!.description} +
`)} + +
+ + + + Criteria + Definition + + + ${this.#value.details.map((detail: PersonalisationGroupDetail, index: number) => + html` + ${this.#getCriteriaName(detail.alias)} + + ${until( + this.#translateDetailDefinition(detail).then((t) => html`${t}`), + html``, + )} + + + this.#editCriteria(index)} + > + this.#removeCriteria(index)} + > + + ` + )} + + +
`; + } + + static styles = [ + css` + table.group-settings td { + vertical-align: top; + padding-right: 10px; + } + + table.group-settings td.label { + padding-top: 4px; + } + + .help-inline { + margin: 4px; + font-size: 12px; + color: var(--uui-color-text-alt); + } + `, + ]; +} + +export default PersonalisationGroupDefinitionInput; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: PersonalisationGroupDefinitionInput; + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/property-editor/group-definition/group-definition-property-editor.element.ts b/PersonalisationGroups/Client/src/property-editor/group-definition/group-definition-property-editor.element.ts new file mode 100644 index 0000000..58ed4be --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/group-definition/group-definition-property-editor.element.ts @@ -0,0 +1,40 @@ +import { + html, + customElement, + property, +} from "@umbraco-cms/backoffice/external/lit"; +import type { UmbPropertyEditorUiElement } from "@umbraco-cms/backoffice/extension-registry"; +import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element"; +import { PersonalisationGroup } from "../../types"; +import PersonalisationGroupDefinitionInput from "./group-definition-input.element"; + +const elementName = "personalisation-group-definition-property-editor"; + +@customElement(elementName) +export class PersonalisationGroupDefinitionPropertyUiElement extends UmbLitElement implements UmbPropertyEditorUiElement { + + @property({ type: Object }) + value: + | PersonalisationGroup + | undefined = undefined; + + #onChange(event: CustomEvent) { + this.value = (event.target as PersonalisationGroupDefinitionInput).value; + this.dispatchEvent(new CustomEvent('property-value-change')); +} + +render() { + return html``; + } + +} + +export default PersonalisationGroupDefinitionPropertyUiElement; + +declare global { + interface HTMLElementTagNameMap { + [elementName]: PersonalisationGroupDefinitionPropertyUiElement; + } +} diff --git a/PersonalisationGroups/Client/src/property-editor/group-definition/manifests.ts b/PersonalisationGroups/Client/src/property-editor/group-definition/manifests.ts new file mode 100644 index 0000000..9b6a418 --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/group-definition/manifests.ts @@ -0,0 +1,14 @@ +import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifest: ManifestPropertyEditorUi = { + type: 'propertyEditorUi', + alias: 'PersonalisationGroups.PropertyEditorUi.GroupDefinition', + name: 'Personalisation Group Definition Editor', + js: () => import('./group-definition-property-editor.element'), + meta: { + label: 'Personalisation Group Definition Editor', + propertyEditorSchemaAlias: 'PersonalisationGroups.GroupDefinition', + icon: 'icon-operator', + group: 'personalisation' + }, +}; diff --git a/PersonalisationGroups/Client/src/property-editor/index.ts b/PersonalisationGroups/Client/src/property-editor/index.ts new file mode 100644 index 0000000..6d1ec6b --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/index.ts @@ -0,0 +1,2 @@ +export * from "./group-definition/group-definition-property-editor.element.js"; +export * from "./group-definition/group-definition-input.element.js"; \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/property-editor/manifests.ts b/PersonalisationGroups/Client/src/property-editor/manifests.ts new file mode 100644 index 0000000..8382ead --- /dev/null +++ b/PersonalisationGroups/Client/src/property-editor/manifests.ts @@ -0,0 +1,7 @@ +import { manifest as groupDefinitionPropertyEditorUiManifest } from './group-definition/manifests.js'; +import { manifests as criteriaManifests } from './criteria/manifests.ts'; + +export const manifests = [ + groupDefinitionPropertyEditorUiManifest, + ...criteriaManifests +]; diff --git a/PersonalisationGroups/Client/src/translator/authentication-status-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/authentication-status-definition-detail.translator.ts new file mode 100644 index 0000000..9b6714e --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/authentication-status-definition-detail.translator.ts @@ -0,0 +1,16 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class AuthenticationStatusDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const authenticationStatusDetails = JSON.parse(definition); + translation = authenticationStatusDetails.isAuthenticated ? "Is logged in." : "Is not logged in."; + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/continent-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/continent-definition-detail.translator.ts new file mode 100644 index 0000000..7ab5fbc --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/continent-definition-detail.translator.ts @@ -0,0 +1,44 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class ContinentDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const selectedContinentDetails = JSON.parse(definition); + if (selectedContinentDetails.match === "CouldNotBeLocated") { + translation = "Visitor cannot be located"; + } else { + translation = "Visitor is "; + switch (selectedContinentDetails.match) { + case "IsLocatedIn": + translation += "located"; + break; + case "IsNotLocatedIn": + translation += "not located"; + break; + } + + translation += " in: "; + + for (let i = 0; i < selectedContinentDetails.codes.length; i++) { + if (i > 0 && i === selectedContinentDetails.codes.length - 1) { + translation += " or "; + } else if (i > 0) { + translation += ", "; + } + + // Versions 0.2.5 and later store the continent name, before that we just had the code. + // So display the name if we have it, otherwise just the code. + translation += selectedContinentDetails.names + ? selectedContinentDetails.names[i] + : selectedContinentDetails.codes[i].toUpperCase(); + } + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/cookie-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/cookie-definition-detail.translator.ts new file mode 100644 index 0000000..87055a6 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/cookie-definition-detail.translator.ts @@ -0,0 +1,48 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class CookieDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const selectedCookieDetails = JSON.parse(definition); + translation = "Cookie with key '" + selectedCookieDetails.key + "' "; + switch (selectedCookieDetails.match) { + case "Exists": + translation += "is present."; + break; + case "DoesNotExist": + translation += "is absent."; + break; + case "MatchesValue": + translation += "matches value '" + selectedCookieDetails.value + "'."; + break; + case "ContainsValue": + translation += "contains value '" + selectedCookieDetails.value + "'."; + break; + case "GreaterThanValue": + translation += "is greater than value '" + selectedCookieDetails.value + "'."; + break; + case "GreaterThanOrEqualToValue": + translation += "is greater than or equal to value '" + selectedCookieDetails.value + "'."; + break; + case "LessThanValue": + translation += "is less than value '" + selectedCookieDetails.value + "'."; + break; + case "LessThanOrEqualToValue": + translation += "is less than or equal to value '" + selectedCookieDetails.value + "'."; + break; + case "MatchesRegex": + translation += "matches regular expression '" + selectedCookieDetails.value + "'."; + break; + case "DoesNotMatchRegex": + translation += "does not match regular expression '" + selectedCookieDetails.value + "'."; + break; + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/country-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/country-definition-detail.translator.ts new file mode 100644 index 0000000..0df0489 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/country-definition-detail.translator.ts @@ -0,0 +1,44 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class CountryDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const selectedCountryDetails = JSON.parse(definition); + if (selectedCountryDetails.match === "CouldNotBeLocated") { + translation = "Visitor cannot be located"; + } else { + translation = "Visitor is "; + switch (selectedCountryDetails.match) { + case "IsLocatedIn": + translation += "located"; + break; + case "IsNotLocatedIn": + translation += "not located"; + break; + } + + translation += " in: "; + + for (let i = 0; i < selectedCountryDetails.codes.length; i++) { + if (i > 0 && i === selectedCountryDetails.codes.length - 1) { + translation += " or "; + } else if (i > 0) { + translation += ", "; + } + + // Versions 0.2.5 and later store the country name, before that we just had the code. + // So display the name if we have it, otherwise just the code. + translation += selectedCountryDetails.names + ? selectedCountryDetails.names[i] + : selectedCountryDetails.codes[i].toUpperCase(); + } + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/day-of-week-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/day-of-week-definition-detail.translator.ts new file mode 100644 index 0000000..ca5c0e3 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/day-of-week-definition-detail.translator.ts @@ -0,0 +1,24 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class DayOfWeekDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; + const selectedDays = JSON.parse(definition); + + for (let i = 0; i < selectedDays.length; i++) { + if (translation.length > 0) { + translation += ", "; + } + + translation += days[selectedDays[i] - 1]; + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/host-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/host-definition-detail.translator.ts new file mode 100644 index 0000000..4d45f5c --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/host-definition-detail.translator.ts @@ -0,0 +1,30 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class HostDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + var selectedHostDetails = JSON.parse(definition); + translation = "Host value "; + switch (selectedHostDetails.match) { + case "MatchesValue": + translation += "matches '" + selectedHostDetails.value + "'."; + break; + case "DoesNotMatchValue": + translation += "does not match '" + selectedHostDetails.value + "'."; + break; + case "ContainsValue": + translation += "contains '" + selectedHostDetails.value + "'."; + break; + case "DoesNotContainValue": + translation += "does not contain '" + selectedHostDetails.value + "'."; + break; + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/index.ts b/PersonalisationGroups/Client/src/translator/index.ts new file mode 100644 index 0000000..dec344e --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/index.ts @@ -0,0 +1,18 @@ +export * from "./authentication-status-definition-detail.translator.js"; +export * from "./continent-definition-detail.translator.js"; +export * from "./cookie-definition-detail.translator.js"; +export * from "./country-definition-detail.translator.js"; +export * from "./day-of-week-definition-detail.translator.js"; +export * from "./host-definition-detail.translator.js"; +export * from "./member-group-definition-detail.translator.js"; +export * from "./member-profile-field-definition-detail.translator.js"; +export * from "./member-type-definition-detail.translator.js"; +export * from "./month-of-year-definition-detail.translator.js"; +export * from "./number-of-visits-definition-detail.translator.js"; +export * from "./pages-viewed-definition-detail.translator.js"; +export * from "./querystring-definition-detail.translator.js"; +export * from "./referral-definition-detail.translator.js"; +export * from "./region-definition-detail.translator.js"; +export * from "./session-definition-detail.translator.js"; +export * from "./time-of-day-definition-detail.translator.js"; + diff --git a/PersonalisationGroups/Client/src/translator/manifests.ts b/PersonalisationGroups/Client/src/translator/manifests.ts new file mode 100644 index 0000000..c94f3d9 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/manifests.ts @@ -0,0 +1,142 @@ +import type { ManifestPersonalisationGroupDefinitionDetailTranslator } from "./translator.interface.js"; +import { + AuthenticationStatusDefinitionDetailTranslator, + ContinentDefinitionDetailTranslator, + CookieDefinitionDetailTranslator, + CountryDefinitionDetailTranslator, + DayOfWeekDefinitionDetailTranslator, + HostDefinitionDetailTranslator, + MemberGroupDefinitionDetailTranslator, + MemberProfileFieldDefinitionDetailTranslator, + MemberTypeDefinitionDetailTranslator, + MonthOfYearDefinitionDetailTranslator, + NumberOfVisitsDefinitionDetailTranslator, + PagesViewedDefinitionDetailTranslator, + QueryStringDefinitionDetailTranslator, + ReferralDefinitionDetailTranslator, + RegionDefinitionDetailTranslator, + SessionDefinitionDetailTranslator, + TimeOfDayDefinitionDetailTranslator, +} from "./index.js"; + +export const manifests: Array = [ + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.AuthenticationStatus", + name: "Authentication Status Translator", + criteriaAlias: "authenticationStatus", + api: AuthenticationStatusDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.Continent", + name: "Continent Translator", + criteriaAlias: "continent", + api: ContinentDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.Cookie", + name: "Cookie Translator", + criteriaAlias: "cookie", + api: CookieDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.Country", + name: "Country Translator", + criteriaAlias: "country", + api: CountryDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.DayOfWeek", + name: "Day Of Week Translator", + criteriaAlias: "dayOfWeek", + api: DayOfWeekDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.Host", + name: "Host Translator", + criteriaAlias: "host", + api: HostDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.MemberGroup", + name: "Member Group Translator", + criteriaAlias: "memberGroup", + api: MemberGroupDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.MemberProfileField", + name: "Member Profile Field Translator", + criteriaAlias: "memberProfileField", + api: MemberProfileFieldDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.MemberType", + name: "Member Type Translator", + criteriaAlias: "memberType", + api: MemberTypeDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.MonthOfYear", + name: "Month Of Year Translator", + criteriaAlias: "monthOfYear", + api: MonthOfYearDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.NumberOfVisits", + name: "Number Of Visits Translator", + criteriaAlias: "numberOfVisits", + api: NumberOfVisitsDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.PagesViewed", + name: "Pages Viewed Translator", + criteriaAlias: "pagesViewed", + api: PagesViewedDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.QueryString", + name: "QueryString Translator", + criteriaAlias: "querystring", + api: QueryStringDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.Referral", + name: "Referral Translator", + criteriaAlias: "referral", + api: ReferralDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.Region", + name: "Region Translator", + criteriaAlias: "region", + api: RegionDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.Session", + name: "Session Translator", + criteriaAlias: "session", + api: SessionDefinitionDetailTranslator, + }, + { + type: "PersonalisationGroupDetailDefinitionTranslator", + alias: "PersonalisationGroup.DefinitionDetailTranslator.TimeOfDay", + name: "Time Of Day Translator", + criteriaAlias: "timeOfDay", + api: TimeOfDayDefinitionDetailTranslator, + }, +]; diff --git a/PersonalisationGroups/Client/src/translator/member-group-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/member-group-definition-detail.translator.ts new file mode 100644 index 0000000..a2f2904 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/member-group-definition-detail.translator.ts @@ -0,0 +1,18 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class MemberGroupDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const memberTypeDetails = JSON.parse(definition); + translation = (memberTypeDetails.match === "IsInGroup" ? "Is in group " : "Is not in group ") + + "'" + memberTypeDetails.groupName + "'."; + + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/member-profile-field-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/member-profile-field-definition-detail.translator.ts new file mode 100644 index 0000000..3d32e4d --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/member-profile-field-definition-detail.translator.ts @@ -0,0 +1,36 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class MemberProfileFieldDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const profileFieldDetails = JSON.parse(definition); + translation = "Field with alias '" + profileFieldDetails.alias + "' "; + switch (profileFieldDetails.match) { + case "MatchesValue": + translation += "matches value '" + profileFieldDetails.value + "'."; + break; + case "ContainsValue": + translation += "contains value '" + profileFieldDetails.value + "'."; + break; + case "GreaterThanValue": + translation += "is greater than value '" + profileFieldDetails.value + "'."; + break; + case "GreaterThanOrEqualToValue": + translation += "is greater than or equal to value '" + profileFieldDetails.value + "'."; + break; + case "LessThanValue": + translation += "is less than value '" + profileFieldDetails.value + "'."; + break; + case "LessThanOrEqualToValue": + translation += "is less than or equal to value '" + profileFieldDetails.value + "'."; + break; + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/member-type-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/member-type-definition-detail.translator.ts new file mode 100644 index 0000000..af96f52 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/member-type-definition-detail.translator.ts @@ -0,0 +1,18 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class MemberTypeDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const memberTypeDetails = JSON.parse(definition); + translation = (memberTypeDetails.match === "IsOfType" ? "Is of type " : "Is not of type ") + + "'" + memberTypeDetails.typeName + "'."; + + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/month-of-year-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/month-of-year-definition-detail.translator.ts new file mode 100644 index 0000000..96f9d20 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/month-of-year-definition-detail.translator.ts @@ -0,0 +1,24 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class MonthOfYearDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; + const selectedMonths = JSON.parse(definition); + + for (let i = 0; i < selectedMonths.length; i++) { + if (translation.length > 0) { + translation += ", "; + } + + translation += months[selectedMonths[i] - 1]; + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/number-of-visits-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/number-of-visits-definition-detail.translator.ts new file mode 100644 index 0000000..aaffbc4 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/number-of-visits-definition-detail.translator.ts @@ -0,0 +1,30 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class NumberOfVisitsDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const selectedNumberOfVisitsDetails = JSON.parse(definition); + translation = "Visitor has visited the site "; + switch (selectedNumberOfVisitsDetails.match) { + case "MoreThan": + translation += "more than"; + break; + case "LessThan": + translation += "less than"; + break; + case "Exactly": + translation += "exactly"; + break; + } + + translation += " " + selectedNumberOfVisitsDetails.number + + " time" + (selectedNumberOfVisitsDetails.number === 1 ? "" : "s"); + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/pages-viewed-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/pages-viewed-definition-detail.translator.ts new file mode 100644 index 0000000..b54f682 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/pages-viewed-definition-detail.translator.ts @@ -0,0 +1,33 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class PagesViewedDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const selectedPagesViewedDetails = JSON.parse(definition); + translation = "Visitor has "; + switch (selectedPagesViewedDetails.match) { + case "ViewedAny": + translation += "viewed any of"; + break; + case "ViewedAll": + translation += "viewed all"; + break; + case "NotViewedAny": + translation += "not viewed any of"; + break; + case "NotViewedAll": + translation += "not viewed all"; + break; + } + + translation += " the " + selectedPagesViewedDetails.nodeKeys.length + + " selected page" + (selectedPagesViewedDetails.nodeKeys.length === 1 ? "" : "s"); + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/querystring-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/querystring-definition-detail.translator.ts new file mode 100644 index 0000000..67e8626 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/querystring-definition-detail.translator.ts @@ -0,0 +1,36 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class QueryStringDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + var selectedQuerystringDetails = JSON.parse(definition); + translation = "Key of '" + selectedQuerystringDetails.key + "' "; + switch (selectedQuerystringDetails.match) { + case "MatchesValue": + translation += "matches '" + selectedQuerystringDetails.value + "'."; + break; + case "DoesNotMatchValue": + translation += "does not match '" + selectedQuerystringDetails.value + "'."; + break; + case "ContainsValue": + translation += "contains '" + selectedQuerystringDetails.value + "'."; + break; + case "DoesNotContainValue": + translation += "does not contain '" + selectedQuerystringDetails.value + "'."; + break; + case "MatchesRegex": + translation += "matches regular expression '" + selectedQuerystringDetails.value + "'."; + break; + case "DoesNotMatchRegex": + translation += "does not match regular expression '" + selectedQuerystringDetails.value + "'."; + break; + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/referral-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/referral-definition-detail.translator.ts new file mode 100644 index 0000000..48ff8af --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/referral-definition-detail.translator.ts @@ -0,0 +1,30 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class ReferralDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const selectedReferrerDetails = JSON.parse(definition); + translation = "Referrer value "; + switch (selectedReferrerDetails.match) { + case "MatchesValue": + translation += "matches '" + selectedReferrerDetails.value + "'."; + break; + case "DoesNotMatchValue": + translation += "does not match '" + selectedReferrerDetails.value + "'."; + break; + case "ContainsValue": + translation += "contains '" + selectedReferrerDetails.value + "'."; + break; + case "DoesNotContainValue": + translation += "does not contain '" + selectedReferrerDetails.value + "'."; + break; + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/region-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/region-definition-detail.translator.ts new file mode 100644 index 0000000..6086246 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/region-definition-detail.translator.ts @@ -0,0 +1,43 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class RegionDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + + var translation = ""; + if (definition) { + var selectedRegionDetails = JSON.parse(definition); + if (selectedRegionDetails.match === "CouldNotBeLocated") { + translation = "Visitor cannot be located"; + } else { + translation = "Visitor is "; + switch (selectedRegionDetails.match) { + case "IsLocatedIn": + translation += "located"; + break; + case "IsNotLocatedIn": + translation += "not located"; + break; + } + + translation += " in: "; + + for (let i = 0; i < selectedRegionDetails.names.length; i++) { + if (i > 0 && i === selectedRegionDetails.names.length - 1) { + translation += " or "; + } else if (i > 0) { + translation += ", "; + } + + translation += selectedRegionDetails.names[i]; + } + + translation += " (" + selectedRegionDetails.countryCode + ")"; + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/session-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/session-definition-detail.translator.ts new file mode 100644 index 0000000..09381e1 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/session-definition-detail.translator.ts @@ -0,0 +1,48 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class SessionDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + if (definition) { + const selectedCookieDetails = JSON.parse(definition); + translation = "Session key '" + selectedCookieDetails.key + "' "; + switch (selectedCookieDetails.match) { + case "Exists": + translation += "is present."; + break; + case "DoesNotExist": + translation += "is absent."; + break; + case "MatchesValue": + translation += "matches value '" + selectedCookieDetails.value + "'."; + break; + case "ContainsValue": + translation += "contains value '" + selectedCookieDetails.value + "'."; + break; + case "GreaterThanValue": + translation += "is greater than value '" + selectedCookieDetails.value + "'."; + break; + case "GreaterThanOrEqualToValue": + translation += "is greater than or equal to value '" + selectedCookieDetails.value + "'."; + break; + case "LessThanValue": + translation += "is less than value '" + selectedCookieDetails.value + "'."; + break; + case "LessThanOrEqualToValue": + translation += "is less than or equal to value '" + selectedCookieDetails.value + "'."; + break; + case "MatchesRegex": + translation += "matches regular expression '" + selectedCookieDetails.value + "'."; + break; + case "DoesNotMatchRegex": + translation += "does not match regular expression '" + selectedCookieDetails.value + "'."; + break; + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/time-of-day-definition-detail.translator.ts b/PersonalisationGroups/Client/src/translator/time-of-day-definition-detail.translator.ts new file mode 100644 index 0000000..6066bf4 --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/time-of-day-definition-detail.translator.ts @@ -0,0 +1,29 @@ +import { PersonalisationGroupDefinitionDetailTranslatorApi } from "./translator.interface.js"; + +export class TimeOfDayDefinitionDetailTranslator implements PersonalisationGroupDefinitionDetailTranslatorApi { + translate(definition: string) { + let translation = ""; + + function formatTime(time: number) { + const timeAsString = time.toString().padStart(4, "0"); + return timeAsString.substring(0, timeAsString.length - 2) + ":" + timeAsString.substring(timeAsString.length - 2); + } + + if (definition) { + var selectedTimes = JSON.parse(definition); + + for (var i = 0; i < selectedTimes.length; i++) { + if (translation.length > 0) { + translation += ", "; + } + + translation += formatTime(selectedTimes[i].from) + " - " + formatTime(selectedTimes[i].to); + } + } + + return translation; + } + + destroy() { + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/translator/translator.interface.ts b/PersonalisationGroups/Client/src/translator/translator.interface.ts new file mode 100644 index 0000000..bc5206f --- /dev/null +++ b/PersonalisationGroups/Client/src/translator/translator.interface.ts @@ -0,0 +1,12 @@ +import type { UmbApi } from "@umbraco-cms/backoffice/extension-api"; +import type { ManifestElementAndApi, ApiLoaderProperty } from "@umbraco-cms/backoffice/extension-api"; + +export interface PersonalisationGroupDefinitionDetailTranslatorApi extends UmbApi { + translate(definition: string) : string; +} + +export interface ManifestPersonalisationGroupDefinitionDetailTranslator extends ManifestElementAndApi { + type: "PersonalisationGroupDetailDefinitionTranslator"; + criteriaAlias: string; + api: ApiLoaderProperty; + } \ No newline at end of file diff --git a/PersonalisationGroups/Client/src/types.ts b/PersonalisationGroups/Client/src/types.ts new file mode 100644 index 0000000..878ff98 --- /dev/null +++ b/PersonalisationGroups/Client/src/types.ts @@ -0,0 +1,11 @@ +export type PersonalisationGroup = { + match: string; + duration: string; + score: Number, + details: Array +}; + +export type PersonalisationGroupDetail = { + alias: string, + definition: string +}; \ No newline at end of file diff --git a/PersonalisationGroups/Client/tsconfig.json b/PersonalisationGroups/Client/tsconfig.json new file mode 100644 index 0000000..55cba08 --- /dev/null +++ b/PersonalisationGroups/Client/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "experimentalDecorators": true, + "useDefineForClassFields": false, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + + "paths": { + "@personalisationgroups/generated": [ + "./generated/index.ts" + ] + } + }, + "include": ["src"] +} diff --git a/PersonalisationGroups/Client/vite.config.ts b/PersonalisationGroups/Client/vite.config.ts new file mode 100644 index 0000000..c5c4c9f --- /dev/null +++ b/PersonalisationGroups/Client/vite.config.ts @@ -0,0 +1,20 @@ +import { defineConfig } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; +import { outputPath } from "./config.outputPath.js"; + +export default defineConfig({ + build: { + lib: { + entry: "src/index.ts", + formats: ["es"], + }, + outDir: outputPath, + emptyOutDir: true, + sourcemap: true, + rollupOptions: { + external: [/^@umbraco-cms/], + onwarn: () => { }, + }, + }, + plugins: [tsconfigPaths()] +}); \ No newline at end of file diff --git a/PersonalisationGroups/Configuration/PersonalisationGroupsConfig.cs b/PersonalisationGroups/Configuration/PersonalisationGroupsConfig.cs new file mode 100644 index 0000000..07fe90d --- /dev/null +++ b/PersonalisationGroups/Configuration/PersonalisationGroupsConfig.cs @@ -0,0 +1,50 @@ +using Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +namespace Our.Umbraco.PersonalisationGroups.Configuration; + +public class PersonalisationGroupsConfig +{ + public bool DisablePackage { get; set; } = false; + + public string GroupPickerAlias { get; set; } = AppConstants.DefaultGroupPickerAlias; + + public string GeoLocationCountryDatabasePath { get; set; } = AppConstants.DefaultGeoLocationCountryDatabasePath; + + public string GeoLocationCityDatabasePath { get; set; } = AppConstants.DefaultGeoLocationCityDatabasePath; + + public string GeoLocationRegionListPath { get; set; } = string.Empty; + + public string IncludeCriteria { get; set; } = string.Empty; + + public string ExcludeCriteria { get; set; } = string.Empty; + + public int NumberOfVisitsTrackingCookieExpiryInDays { get; set; } = AppConstants.DefaultNumberOfVisitsTrackingCookieExpiryInDays; + + public int ViewedPagesTrackingCookieExpiryInDays { get; set; } = AppConstants.DefaultViewedPagesTrackingCookieExpiryInDays; + + public string CookieKeyForTrackingNumberOfVisits { get; set; } = AppConstants.DefaultCookieKeyForTrackingNumberOfVisits; + + public string CookieKeyForTrackingIfSessionAlreadyTracked { get; set; } = AppConstants.DefaultCookieKeyForTrackingIfSessionAlreadyTracked; + + public string CookieKeyForTrackingPagesViewed { get; set; } = AppConstants.DefaultCookieKeyForTrackingPagesViewed; + + public string CookieKeyForSessionMatchedGroups { get; set; } = AppConstants.DefaultCookieKeyForSessionMatchedGroups; + + public string CookieKeyForPersistentMatchedGroups { get; set; } = AppConstants.DefaultCookieKeyForPersistentMatchedGroups; + + public string CookieKeyForTrackingCookiesDeclined { get; set; } = AppConstants.DefaultCookieKeyForTrackingCookiesDeclined; + + public string SessionKeyForTrackingCookiesDeclined { get; set; } = AppConstants.DefaultSessionKeyForTrackingCookiesDeclined; + + public int PersistentMatchedGroupsCookieExpiryInDays { get; set; } = AppConstants.DefaultPersistentMatchedGroupsCookieExpiryInDays; + + public string TestFixedIp { get; set; } = string.Empty; + + public CountryCodeProvider CountryCodeProvider { get; set; } = CountryCodeProvider.MaxMindDatabase; + + public string CdnCountryCodeHttpHeaderName { get; set; } = AppConstants.DefaultCdnCountryCodeHttpHeaderName; + + public bool DisableHttpContextItemsUseInCookieOperations { get; set; } = false; + + public bool DisableUserActivityTracking { get; set; } = false; +} diff --git a/PersonalisationGroups/Criteria/AuthenticationStatus/AuthenticationStatusPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/AuthenticationStatus/AuthenticationStatusPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..14b12c1 --- /dev/null +++ b/PersonalisationGroups/Criteria/AuthenticationStatus/AuthenticationStatusPersonalisationGroupCriteria.cs @@ -0,0 +1,46 @@ +using System; +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.AuthenticationStatus; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.AuthenticationStatus; + +/// +/// Implements a personalisation group criteria based on whether the user is logged on or not +/// +public class AuthenticationStatusPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IAuthenticationStatusProvider _authenticationStatusProvider; + + public AuthenticationStatusPersonalisationGroupCriteria(IAuthenticationStatusProvider authenticationStatusProvider) + { + _authenticationStatusProvider = authenticationStatusProvider; + } + + public string Name => "Authentication status"; + + public string Alias => "authenticationStatus"; + + public string Description => "Matches visitor session with their authentication status"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + AuthenticationStatusSetting authenticationStatusSetting; + try + { + authenticationStatusSetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + return (authenticationStatusSetting.IsAuthenticated && _authenticationStatusProvider.IsAuthenticated()) || + (!authenticationStatusSetting.IsAuthenticated && !_authenticationStatusProvider.IsAuthenticated()); + } +} diff --git a/PersonalisationGroups/Criteria/AuthenticationStatus/AuthenticationStatusSetting.cs b/PersonalisationGroups/Criteria/AuthenticationStatus/AuthenticationStatusSetting.cs new file mode 100644 index 0000000..9a20480 --- /dev/null +++ b/PersonalisationGroups/Criteria/AuthenticationStatus/AuthenticationStatusSetting.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.AuthenticationStatus; + +public class AuthenticationStatusSetting +{ + public bool IsAuthenticated { get; set; } +} diff --git a/PersonalisationGroups/Criteria/Continent/ContinentPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/Continent/ContinentPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..3f48bfb --- /dev/null +++ b/PersonalisationGroups/Criteria/Continent/ContinentPersonalisationGroupCriteria.cs @@ -0,0 +1,75 @@ +using System; +using System.Linq; +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; +using Our.Umbraco.PersonalisationGroups.Providers.Ip; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.Continent; + +/// +/// Implements a personalisation group criteria based on the country derived from the vistor's IP address +/// +public class ContinentPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IIpProvider _ipProvider; + private readonly IGeoLocationProvider _geoLocationProvider; + + public ContinentPersonalisationGroupCriteria(IIpProvider ipProvider, IGeoLocationProvider geoLocationProvider) + { + _ipProvider = ipProvider; + _geoLocationProvider = geoLocationProvider; + } + + public string Name => "Continent"; + + public string Alias => "continent"; + + public string Description => "Matches visitor continent derived from their IP address to a given list of countries"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + ContinentSetting countrySetting; + try + { + countrySetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var ip = _ipProvider.GetIp(); + if (!string.IsNullOrEmpty(ip)) + { + var country = _geoLocationProvider.GetContinentFromIp(ip); + if (country != null) + { + if (countrySetting.Match == GeoLocationSettingMatch.CouldNotBeLocated) + { + // We can't locate, so return false. + return false; + } + + var matchedContinent = countrySetting.Codes + .Any(x => string.Equals(x, country.Code, StringComparison.InvariantCultureIgnoreCase)); + switch (countrySetting.Match) + { + case GeoLocationSettingMatch.IsLocatedIn: + return matchedContinent; + case GeoLocationSettingMatch.IsNotLocatedIn: + return !matchedContinent; + default: + return false; + } + } + } + + return countrySetting.Match == GeoLocationSettingMatch.CouldNotBeLocated; + } +} diff --git a/PersonalisationGroups/Criteria/Continent/ContinentSetting.cs b/PersonalisationGroups/Criteria/Continent/ContinentSetting.cs new file mode 100644 index 0000000..6711d19 --- /dev/null +++ b/PersonalisationGroups/Criteria/Continent/ContinentSetting.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.Continent; + +public class ContinentSetting +{ + public required GeoLocationSettingMatch Match { get; set; } + + public IEnumerable Codes { get; set; } = Enumerable.Empty(); +} diff --git a/PersonalisationGroups/Criteria/Cookie/CookiePersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/Cookie/CookiePersonalisationGroupCriteria.cs new file mode 100644 index 0000000..5b2fba2 --- /dev/null +++ b/PersonalisationGroups/Criteria/Cookie/CookiePersonalisationGroupCriteria.cs @@ -0,0 +1,96 @@ +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.Cookie; +using System; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.Cookie; + +/// +/// Implements a personalisation group criteria based on the presence, absence or value of a cookie +/// +public class CookiePersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly ICookieProvider _cookieProvider; + + public CookiePersonalisationGroupCriteria(ICookieProvider cookieProvider) + { + _cookieProvider = cookieProvider; + } + + public string Name => "Cookie"; + + public string Alias => "cookie"; + + public string Description => "Matches visitor session with the presence, absence or value of a cookie"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + CookieSetting cookieSetting; + try + { + cookieSetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + if (string.IsNullOrEmpty(cookieSetting.Key)) + { + throw new ArgumentNullException("key", "Cookie key not set"); + } + + var cookieExists = _cookieProvider.CookieExists(cookieSetting.Key); + string? cookieValue = null; + if (cookieExists) + { + cookieValue = _cookieProvider.GetCookieValue(cookieSetting.Key); + } + + switch (cookieSetting.Match) + { + case CookieSettingMatch.Exists: + return cookieExists; + case CookieSettingMatch.DoesNotExist: + return !cookieExists; + case CookieSettingMatch.MatchesValue: + return cookieExists && MatchesValue(cookieValue, cookieSetting.Value); + case CookieSettingMatch.ContainsValue: + return cookieExists && ContainsValue(cookieValue, cookieSetting.Value); + case CookieSettingMatch.GreaterThanValue: + case CookieSettingMatch.GreaterThanOrEqualToValue: + case CookieSettingMatch.LessThanValue: + case CookieSettingMatch.LessThanOrEqualToValue: + return cookieExists && + CompareValues(cookieValue, cookieSetting.Value, GetComparison(cookieSetting.Match)); + case CookieSettingMatch.MatchesRegex: + return cookieExists && MatchesRegex(cookieValue, cookieSetting.Value); + case CookieSettingMatch.DoesNotMatchRegex: + return !cookieExists || !MatchesRegex(cookieValue, cookieSetting.Value); + default: + return false; + } + } + + private static Comparison GetComparison(CookieSettingMatch settingMatch) + { + switch (settingMatch) + { + case CookieSettingMatch.GreaterThanValue: + return Comparison.GreaterThan; + case CookieSettingMatch.GreaterThanOrEqualToValue: + return Comparison.GreaterThanOrEqual; + case CookieSettingMatch.LessThanValue: + return Comparison.LessThan; + case CookieSettingMatch.LessThanOrEqualToValue: + return Comparison.LessThanOrEqual; + default: + throw new ArgumentException("Setting supplied does not match a comparison type", nameof(settingMatch)); + } + } +} diff --git a/PersonalisationGroups/Criteria/Cookie/CookieSetting.cs b/PersonalisationGroups/Criteria/Cookie/CookieSetting.cs new file mode 100644 index 0000000..45d610a --- /dev/null +++ b/PersonalisationGroups/Criteria/Cookie/CookieSetting.cs @@ -0,0 +1,24 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.Cookie; + +public enum CookieSettingMatch +{ + Exists, + DoesNotExist, + MatchesValue, + ContainsValue, + GreaterThanValue, + GreaterThanOrEqualToValue, + LessThanValue, + LessThanOrEqualToValue, + MatchesRegex, + DoesNotMatchRegex, +} + +public class CookieSetting +{ + public required string Key { get; set; } + + public required CookieSettingMatch Match { get; set; } + + public required string Value { get; set; } +} diff --git a/PersonalisationGroups/Criteria/Country/CountryPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/Country/CountryPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..1affad3 --- /dev/null +++ b/PersonalisationGroups/Criteria/Country/CountryPersonalisationGroupCriteria.cs @@ -0,0 +1,68 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.Country; + +using System; +using System.Linq; +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +/// +/// Implements a personalisation group criteria based on the country derived from the vistor's IP address +/// +public class CountryPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly ICountryCodeProvider _countryCodeProvider; + + public CountryPersonalisationGroupCriteria(ICountryCodeProvider countryCodeProvider) + { + _countryCodeProvider = countryCodeProvider; + } + + public string Name => "Country"; + + public string Alias => "country"; + + public string Description => "Matches visitor country derived from their IP address to a given list of countries"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + CountrySetting countrySetting; + try + { + countrySetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var countryCode = _countryCodeProvider.GetCountryCode(); + if (!string.IsNullOrEmpty(countryCode)) + { + if (countrySetting.Match == GeoLocationSettingMatch.CouldNotBeLocated) + { + // We can't locate, so return false. + return false; + } + + var matchedCountry = countrySetting.Codes + .Any(x => string.Equals(x, countryCode, StringComparison.InvariantCultureIgnoreCase)); + switch (countrySetting.Match) + { + case GeoLocationSettingMatch.IsLocatedIn: + return matchedCountry; + case GeoLocationSettingMatch.IsNotLocatedIn: + return !matchedCountry; + default: + return false; + } + } + + return countrySetting.Match == GeoLocationSettingMatch.CouldNotBeLocated; + } +} diff --git a/PersonalisationGroups/Criteria/Country/CountrySetting.cs b/PersonalisationGroups/Criteria/Country/CountrySetting.cs new file mode 100644 index 0000000..d6a654d --- /dev/null +++ b/PersonalisationGroups/Criteria/Country/CountrySetting.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.Country; + +public class CountrySetting +{ + public GeoLocationSettingMatch Match { get; set; } + + public IEnumerable Codes { get; set; } = Enumerable.Empty(); +} diff --git a/PersonalisationGroups/Criteria/DayOfWeek/DayOfWeekPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/DayOfWeek/DayOfWeekPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..a2d97a8 --- /dev/null +++ b/PersonalisationGroups/Criteria/DayOfWeek/DayOfWeekPersonalisationGroupCriteria.cs @@ -0,0 +1,44 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.DayOfWeek; + +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.DateTime; +using System; +using System.Linq; + +/// +/// Implements a personalisation group criteria based on the day of the week +/// +public class DayOfWeekPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IDateTimeProvider _dateTimeProvider; + + public DayOfWeekPersonalisationGroupCriteria(IDateTimeProvider dateTimeProvider) + { + _dateTimeProvider = dateTimeProvider; + } + + public string Name => "Day of week"; + + public string Alias => "dayOfWeek"; + + public string Description => "Matches visitor session with defined days of the week"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + try + { + var definedDays = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + return definedDays.Contains((int)_dateTimeProvider.GetCurrentDateTime().DayOfWeek + 1); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + } +} diff --git a/PersonalisationGroups/Criteria/GeoLocationSettingMatch.cs b/PersonalisationGroups/Criteria/GeoLocationSettingMatch.cs new file mode 100644 index 0000000..040b5fb --- /dev/null +++ b/PersonalisationGroups/Criteria/GeoLocationSettingMatch.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria; + +public enum GeoLocationSettingMatch +{ + IsLocatedIn, + IsNotLocatedIn, + CouldNotBeLocated +} diff --git a/PersonalisationGroups/Criteria/Host/HostPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/Host/HostPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..33b0fe8 --- /dev/null +++ b/PersonalisationGroups/Criteria/Host/HostPersonalisationGroupCriteria.cs @@ -0,0 +1,58 @@ +using System; +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.Host; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.Host; + +/// +/// Implements a personalisation group criteria based on the value of the host of the request URL +/// +public class HostPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IHostProvider _hostProvider; + + public HostPersonalisationGroupCriteria(IHostProvider hostProvider) + { + _hostProvider = hostProvider; + } + + public string Name => "Host"; + + public string Alias => "host"; + + public string Description => "Matches visitor with the URL host"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + HostSetting hostSetting; + try + { + hostSetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var host = _hostProvider.GetHost(); + switch (hostSetting.Match) + { + case HostSettingMatch.MatchesValue: + return MatchesValue(host, hostSetting.Value); + case HostSettingMatch.DoesNotMatchValue: + return !MatchesValue(host, hostSetting.Value); + case HostSettingMatch.ContainsValue: + return ContainsValue(host, hostSetting.Value); + case HostSettingMatch.DoesNotContainValue: + return !ContainsValue(host, hostSetting.Value); + default: + return false; + } + } +} diff --git a/PersonalisationGroups/Criteria/Host/HostSetting.cs b/PersonalisationGroups/Criteria/Host/HostSetting.cs new file mode 100644 index 0000000..ab3de78 --- /dev/null +++ b/PersonalisationGroups/Criteria/Host/HostSetting.cs @@ -0,0 +1,16 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.Host; + +public enum HostSettingMatch +{ + MatchesValue, + DoesNotMatchValue, + ContainsValue, + DoesNotContainValue, +} + +public class HostSetting +{ + public required HostSettingMatch Match { get; set; } + + public required string Value { get; set; } +} diff --git a/PersonalisationGroups/Criteria/IPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/IPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..520addc --- /dev/null +++ b/PersonalisationGroups/Criteria/IPersonalisationGroupCriteria.cs @@ -0,0 +1,34 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria; + +/// +/// Specifies an interface for a personalisation group criteria +/// +public interface IPersonalisationGroupCriteria +{ + /// + /// Gets the name of the criteria + /// + string Name { get; } + + /// + /// Gets the unique alias of the criteria + /// + string Alias { get; } + + /// + /// Gets the description of the criteria + /// + string Description { get; } + + /// + /// Gets the client assets folder + /// + string ClientAssetsFolder { get; } + + /// + /// Checks whether the attributes of the current site visitor match the provided definition + /// + /// Definition of the criteria to check + /// True if definition matches visitor + bool MatchesVisitor(string definition); +} diff --git a/PersonalisationGroups/Criteria/MemberGroup/MemberGroupPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/MemberGroup/MemberGroupPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..9895595 --- /dev/null +++ b/PersonalisationGroups/Criteria/MemberGroup/MemberGroupPersonalisationGroupCriteria.cs @@ -0,0 +1,48 @@ +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.MemberGroup; +using System; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.MemberGroup; + +/// +/// Implements a personalisation group criteria based on the presence, absence or value of a session key +/// +public class MemberGroupPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IMemberGroupProvider _memberGroupProvider; + + public MemberGroupPersonalisationGroupCriteria(IMemberGroupProvider memberGroupProvider) + { + _memberGroupProvider = memberGroupProvider; + } + + public string Name => "Member group"; + + public string Alias => "memberGroup"; + + public string Description => "Matches authenticated visitor session with their member group"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + MemberGroupSetting setting; + try + { + setting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var memberGroups = _memberGroupProvider.GetMemberGroups(); + return (setting.Match == MemberGroupSettingMatch.IsInGroup && memberGroups.Contains(setting.GroupName, StringComparer.InvariantCultureIgnoreCase) || + (setting.Match == MemberGroupSettingMatch.IsNotInGroup && !memberGroups.Contains(setting.GroupName, StringComparer.InvariantCultureIgnoreCase))); + } +} diff --git a/PersonalisationGroups/Criteria/MemberGroup/MemberGroupSetting.cs b/PersonalisationGroups/Criteria/MemberGroup/MemberGroupSetting.cs new file mode 100644 index 0000000..5361187 --- /dev/null +++ b/PersonalisationGroups/Criteria/MemberGroup/MemberGroupSetting.cs @@ -0,0 +1,14 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.MemberGroup; + +public enum MemberGroupSettingMatch +{ + IsInGroup, + IsNotInGroup, +} + +public class MemberGroupSetting +{ + public required MemberGroupSettingMatch Match { get; set; } + + public required string GroupName { get; set; } +} diff --git a/PersonalisationGroups/Criteria/MemberProfileField/MemberProfileFieldPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/MemberProfileField/MemberProfileFieldPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..b7e6cd1 --- /dev/null +++ b/PersonalisationGroups/Criteria/MemberProfileField/MemberProfileFieldPersonalisationGroupCriteria.cs @@ -0,0 +1,77 @@ +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.MemberProfileField; +using System; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.MemberProfileField; + +/// +/// Implements a personalisation group criteria based on the presence, absence or value of a session key +/// +public class MemberProfileFieldPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IMemberProfileFieldProvider _memberProfileFieldProvider; + + public MemberProfileFieldPersonalisationGroupCriteria(IMemberProfileFieldProvider memberProfileFieldProvider) + { + _memberProfileFieldProvider = memberProfileFieldProvider; + } + + public string Name => "Member profile field"; + + public string Alias => "memberProfileField"; + + public string Description => "Matches authenticated visitor session with a field on their member profile"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + MemberProfileFieldSetting setting; + try + { + setting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var value = _memberProfileFieldProvider.GetMemberProfileFieldValue(setting.Alias); + + switch (setting.Match) + { + case MemberProfileFieldSettingMatch.MatchesValue: + return MatchesValue(value, setting.Value); + case MemberProfileFieldSettingMatch.DoesNotMatchValue: + return !MatchesValue(value, setting.Value); + case MemberProfileFieldSettingMatch.GreaterThanValue: + case MemberProfileFieldSettingMatch.GreaterThanOrEqualToValue: + case MemberProfileFieldSettingMatch.LessThanValue: + case MemberProfileFieldSettingMatch.LessThanOrEqualToValue: + return CompareValues(value, setting.Value, GetComparison(setting.Match)); + default: + return false; + } + } + + private static Comparison GetComparison(MemberProfileFieldSettingMatch settingMatch) + { + switch (settingMatch) + { + case MemberProfileFieldSettingMatch.GreaterThanValue: + return Comparison.GreaterThan; + case MemberProfileFieldSettingMatch.GreaterThanOrEqualToValue: + return Comparison.GreaterThanOrEqual; + case MemberProfileFieldSettingMatch.LessThanValue: + return Comparison.LessThan; + case MemberProfileFieldSettingMatch.LessThanOrEqualToValue: + return Comparison.LessThanOrEqual; + default: + throw new ArgumentException("Setting supplied does not match a comparison type", nameof(settingMatch)); + } + } +} diff --git a/PersonalisationGroups/Criteria/MemberProfileField/MemberProfileFieldSetting.cs b/PersonalisationGroups/Criteria/MemberProfileField/MemberProfileFieldSetting.cs new file mode 100644 index 0000000..f4a9f8d --- /dev/null +++ b/PersonalisationGroups/Criteria/MemberProfileField/MemberProfileFieldSetting.cs @@ -0,0 +1,20 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.MemberProfileField; + +public enum MemberProfileFieldSettingMatch +{ + MatchesValue, + DoesNotMatchValue, + GreaterThanValue, + GreaterThanOrEqualToValue, + LessThanValue, + LessThanOrEqualToValue, +} + +public class MemberProfileFieldSetting +{ + public required string Alias { get; set; } + + public required MemberProfileFieldSettingMatch Match { get; set; } + + public required string Value { get; set; } +} diff --git a/PersonalisationGroups/Criteria/MemberType/MemberTypePersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/MemberType/MemberTypePersonalisationGroupCriteria.cs new file mode 100644 index 0000000..d20136b --- /dev/null +++ b/PersonalisationGroups/Criteria/MemberType/MemberTypePersonalisationGroupCriteria.cs @@ -0,0 +1,47 @@ +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.MemberType; +using System; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.MemberType; + +/// +/// Implements a personalisation group criteria based on the presence, absence or value of a session key +/// +public class MemberTypePersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IMemberTypeProvider _memberTypeProvider; + + public MemberTypePersonalisationGroupCriteria(IMemberTypeProvider memberTypeProvider) + { + _memberTypeProvider = memberTypeProvider; + } + + public string Name => "Member type"; + + public string Alias => "memberType"; + + public string Description => "Matches authenticated visitor session with their member type"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + MemberTypeSetting setting; + try + { + setting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var memberType = _memberTypeProvider.GetMemberType(); + return (setting.Match == MemberTypeSettingMatch.IsOfType && string.Equals(setting.TypeName, memberType, StringComparison.InvariantCultureIgnoreCase)) || + (setting.Match == MemberTypeSettingMatch.IsNotOfType && !string.Equals(setting.TypeName, memberType, StringComparison.InvariantCultureIgnoreCase)); + } +} diff --git a/PersonalisationGroups/Criteria/MemberType/MemberTypeSetting.cs b/PersonalisationGroups/Criteria/MemberType/MemberTypeSetting.cs new file mode 100644 index 0000000..c6d8d10 --- /dev/null +++ b/PersonalisationGroups/Criteria/MemberType/MemberTypeSetting.cs @@ -0,0 +1,14 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.MemberType; + +public enum MemberTypeSettingMatch +{ + IsOfType, + IsNotOfType, +} + +public class MemberTypeSetting +{ + public required MemberTypeSettingMatch Match { get; set; } + + public required string TypeName { get; set; } +} diff --git a/PersonalisationGroups/Criteria/MonthOfYear/MonthOfYearPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/MonthOfYear/MonthOfYearPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..0d55866 --- /dev/null +++ b/PersonalisationGroups/Criteria/MonthOfYear/MonthOfYearPersonalisationGroupCriteria.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.DateTime; +using System; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.MonthOfYear; + +/// +/// Implements a personalisation group criteria based on the month of the year +/// +public class MonthOfYearPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IDateTimeProvider _dateTimeProvider; + + public MonthOfYearPersonalisationGroupCriteria(IDateTimeProvider dateTimeProvider) + { + _dateTimeProvider = dateTimeProvider; + } + + public string Name => "Month of year"; + + public string Alias => "monthOfYear"; + + public string Description => "Matches visitor session with defined months of the year"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + try + { + var definedMonths = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + return definedMonths.Contains(_dateTimeProvider.GetCurrentDateTime().Month); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + } +} diff --git a/PersonalisationGroups/Criteria/NumberOfVisits/NumberOfVisitsPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/NumberOfVisits/NumberOfVisitsPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..20fd98e --- /dev/null +++ b/PersonalisationGroups/Criteria/NumberOfVisits/NumberOfVisitsPersonalisationGroupCriteria.cs @@ -0,0 +1,59 @@ +using System; +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.NumberOfVisits; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.NumberOfVisits; + +/// +/// Implements a personalisation group criteria based on the whether certain pages (node Ids) have been viewed +/// +public class NumberOfVisitsPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + internal static string CriteriaAlias = "numberOfVisits"; + + private readonly INumberOfVisitsProvider _numberOfVisitsProvider; + + public NumberOfVisitsPersonalisationGroupCriteria(INumberOfVisitsProvider numberOfVisitsProvider) + { + _numberOfVisitsProvider = numberOfVisitsProvider; + } + + public string Name => "Number of visits"; + + public string Alias => CriteriaAlias; + + public string Description => "Matches visitor session with the number of times they have visited the site"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + NumberOfVisitsSetting numberOfVisitsSetting; + try + { + numberOfVisitsSetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var timesVisited = _numberOfVisitsProvider.GetNumberOfVisits(); + + switch (numberOfVisitsSetting.Match) + { + case NumberOfVisitsSettingMatch.MoreThan: + return timesVisited > numberOfVisitsSetting.Number; + case NumberOfVisitsSettingMatch.LessThan: + return timesVisited < numberOfVisitsSetting.Number; + case NumberOfVisitsSettingMatch.Exactly: + return timesVisited == numberOfVisitsSetting.Number; + default: + return false; + } + } +} diff --git a/PersonalisationGroups/Criteria/NumberOfVisits/NumberOfVisitsSetting.cs b/PersonalisationGroups/Criteria/NumberOfVisits/NumberOfVisitsSetting.cs new file mode 100644 index 0000000..3e7e824 --- /dev/null +++ b/PersonalisationGroups/Criteria/NumberOfVisits/NumberOfVisitsSetting.cs @@ -0,0 +1,15 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.NumberOfVisits; + +public enum NumberOfVisitsSettingMatch +{ + MoreThan, + LessThan, + Exactly, +} + +public class NumberOfVisitsSetting +{ + public NumberOfVisitsSettingMatch Match { get; set; } + + public int Number { get; set; } +} diff --git a/PersonalisationGroups/Criteria/PagesViewed/PagesViewedPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/PagesViewed/PagesViewedPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..e598d3b --- /dev/null +++ b/PersonalisationGroups/Criteria/PagesViewed/PagesViewedPersonalisationGroupCriteria.cs @@ -0,0 +1,66 @@ +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Extensions; +using Our.Umbraco.PersonalisationGroups.Providers.PagesViewed; +using System; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.PagesViewed; + +/// +/// Implements a personalisation group criteria based on the whether certain pages (node Ids) have been viewed +/// +public class PagesViewedPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + internal static string CriteriaAlias = "pagesViewed"; + + private readonly IPagesViewedProvider _pagesViewedProvider; + + public PagesViewedPersonalisationGroupCriteria(IPagesViewedProvider pagesViewedProvider) + { + _pagesViewedProvider = pagesViewedProvider; + } + + public string Name => "Pages viewed"; + + public string Alias => CriteriaAlias; + + public string Description => "Matches visitor session with whether certain pages have been viewed"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + PagesViewedSetting pagesViewedSetting; + try + { + pagesViewedSetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var nodeKeysViewed = _pagesViewedProvider.GetNodeKeysViewed(); + + switch (pagesViewedSetting.Match) + { + case PagesViewedSettingMatch.ViewedAny: + return nodeKeysViewed + .ContainsAny(pagesViewedSetting.NodeKeys); + case PagesViewedSettingMatch.ViewedAll: + return nodeKeysViewed + .ContainsAll(pagesViewedSetting.NodeKeys); + case PagesViewedSettingMatch.NotViewedAny: + return !nodeKeysViewed + .ContainsAny(pagesViewedSetting.NodeKeys); + case PagesViewedSettingMatch.NotViewedAll: + return !nodeKeysViewed + .ContainsAll(pagesViewedSetting.NodeKeys); + default: + return false; + } + } +} diff --git a/PersonalisationGroups/Criteria/PagesViewed/PagesViewedSetting.cs b/PersonalisationGroups/Criteria/PagesViewed/PagesViewedSetting.cs new file mode 100644 index 0000000..78e3e26 --- /dev/null +++ b/PersonalisationGroups/Criteria/PagesViewed/PagesViewedSetting.cs @@ -0,0 +1,18 @@ +using System; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.PagesViewed; + +public enum PagesViewedSettingMatch +{ + ViewedAny, + ViewedAll, + NotViewedAny, + NotViewedAll, +} + +public class PagesViewedSetting +{ + public required PagesViewedSettingMatch Match { get; set; } + + public required Guid[] NodeKeys { get; set; } +} diff --git a/PersonalisationGroups/Criteria/PersonalisationGroupCriteriaBase.cs b/PersonalisationGroups/Criteria/PersonalisationGroupCriteriaBase.cs new file mode 100644 index 0000000..feb53c9 --- /dev/null +++ b/PersonalisationGroups/Criteria/PersonalisationGroupCriteriaBase.cs @@ -0,0 +1,125 @@ +using System; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace Our.Umbraco.PersonalisationGroups.Criteria; + +/// +/// Provides common base functionality for personalisation criteria +/// +public abstract class PersonalisationGroupCriteriaBase +{ + public virtual string ClientAssetsFolder => "PersonalisationGroups/Criteria"; + + protected static bool MatchesValue(string? valueFromContext, string valueFromDefinition) + { + if (valueFromContext == null) + { + return false; + } + + return string.Equals(valueFromContext, valueFromDefinition, + StringComparison.InvariantCultureIgnoreCase); + } + + protected static bool ContainsValue(string? valueFromContext, string valueFromDefinition) + { + if (valueFromContext == null) + { + return false; + } + + return CultureInfo.InvariantCulture.CompareInfo + .IndexOf(valueFromContext, valueFromDefinition, CompareOptions.IgnoreCase) >= 0; + } + + protected static bool MatchesRegex(string? valueFromContext, string valueFromDefinition) + { + if (valueFromContext == null) + { + return false; + } + + return Regex.IsMatch(valueFromContext, valueFromDefinition); + } + + protected bool CompareValues(string? value, string definitionValue, Comparison comparison) + { + var result = DateCompare(value, definitionValue, comparison, out bool comparisonMade); + if (comparisonMade) + { + return result; + } + result = NumericCompare(value, definitionValue, comparison, out comparisonMade); + + if (comparisonMade) + { + return result; + } + + return StringCompare(value, definitionValue, comparison); + } + + private static bool DateCompare(string? value, string definitionValue, Comparison comparison, out bool comparisonMade) + { + if (DateTime.TryParse(value, out DateTime dateValue) && DateTime.TryParse(definitionValue, out DateTime dateDefinitionValue)) + { + comparisonMade = true; + switch (comparison) + { + case Comparison.GreaterThan: + return dateValue > dateDefinitionValue; + case Comparison.GreaterThanOrEqual: + return dateValue >= dateDefinitionValue; + case Comparison.LessThan: + return dateValue < dateDefinitionValue; + case Comparison.LessThanOrEqual: + return dateValue <= dateDefinitionValue; + } + } + + comparisonMade = false; + return false; + } + + private static bool NumericCompare(string? value, string definitionValue, Comparison comparison, out bool comparisonMade) + { + decimal decimalValue; + if (decimal.TryParse(value, out decimalValue) && decimal.TryParse(definitionValue, out decimal decimalDefinitionValue)) + { + comparisonMade = true; + switch (comparison) + { + case Comparison.GreaterThan: + return decimalValue > decimalDefinitionValue; + case Comparison.GreaterThanOrEqual: + return decimalValue >= decimalDefinitionValue; + case Comparison.LessThan: + return decimalValue < decimalDefinitionValue; + case Comparison.LessThanOrEqual: + return decimalValue <= decimalDefinitionValue; + } + } + + comparisonMade = false; + return false; + } + + private static bool StringCompare(string? value, string definitionValue, Comparison comparison) + { + var comparisonValue = string.Compare(value, definitionValue, StringComparison.InvariantCultureIgnoreCase); + switch (comparison) + { + case Comparison.GreaterThan: + return comparisonValue > 0; + case Comparison.GreaterThanOrEqual: + return comparisonValue >= 0; + case Comparison.LessThan: + return comparisonValue < 0; + case Comparison.LessThanOrEqual: + return comparisonValue <= 0; + } + + return false; + } +} diff --git a/PersonalisationGroups/Criteria/PersonalisationGroupDefinition.cs b/PersonalisationGroups/Criteria/PersonalisationGroupDefinition.cs new file mode 100644 index 0000000..be70981 --- /dev/null +++ b/PersonalisationGroups/Criteria/PersonalisationGroupDefinition.cs @@ -0,0 +1,31 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria; + +using System.Collections.Generic; +using System.Linq; + +public enum PersonalisationGroupDefinitionMatch +{ + All, + Any +} + +public enum PersonalisationGroupDefinitionDuration +{ + Page, + Session, + Visitor +} + +/// +/// The definition of a personalisation group +/// +public class PersonalisationGroupDefinition +{ + public required PersonalisationGroupDefinitionMatch Match { get; set; } + + public required PersonalisationGroupDefinitionDuration Duration { get; set; } + + public required int Score { get; set; } + + public IEnumerable Details { get; set; } = Enumerable.Empty(); +} diff --git a/PersonalisationGroups/Criteria/PersonalisationGroupDefinitionDetail.cs b/PersonalisationGroups/Criteria/PersonalisationGroupDefinitionDetail.cs new file mode 100644 index 0000000..30c87c2 --- /dev/null +++ b/PersonalisationGroups/Criteria/PersonalisationGroupDefinitionDetail.cs @@ -0,0 +1,11 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria; + +/// +/// The definition of a detail record for a personalisation group +/// +public class PersonalisationGroupDefinitionDetail +{ + public required string Alias { get; set; } + + public required string Definition { get; set; } +} diff --git a/PersonalisationGroups/Criteria/Querystring/QuerystringPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/Querystring/QuerystringPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..3eed6bd --- /dev/null +++ b/PersonalisationGroups/Criteria/Querystring/QuerystringPersonalisationGroupCriteria.cs @@ -0,0 +1,84 @@ +using System; +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.Querystring; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.Querystring; + +public class QuerystringPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IQuerystringProvider _querystringProvider; + + public QuerystringPersonalisationGroupCriteria(IQuerystringProvider querystringProvider) + { + _querystringProvider = querystringProvider; + } + + public string Alias => "querystring"; + + public string Name => "Querystring"; + + public string Description => "Matches visitor based on specific values in the Querystring"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + QuerystringSetting querystringSetting; + try + { + querystringSetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var querystring = _querystringProvider.GetQuerystring(); + + var querystringValue = querystring[querystringSetting.Key]; + + switch (querystringSetting.Match) + { + case QuerystringSettingMatch.MatchesValue: + return MatchesValue(querystringValue, querystringSetting.Value); + case QuerystringSettingMatch.DoesNotMatchValue: + return !MatchesValue(querystringValue, querystringSetting.Value); + case QuerystringSettingMatch.ContainsValue: + return ContainsValue(querystringValue, querystringSetting.Value); + case QuerystringSettingMatch.DoesNotContainValue: + return !ContainsValue(querystringValue, querystringSetting.Value); + case QuerystringSettingMatch.GreaterThanValue: + case QuerystringSettingMatch.GreaterThanOrEqualToValue: + case QuerystringSettingMatch.LessThanValue: + case QuerystringSettingMatch.LessThanOrEqualToValue: + return CompareValues(querystringValue, querystringSetting.Value, GetComparison(querystringSetting.Match)); + case QuerystringSettingMatch.MatchesRegex: + return MatchesRegex(querystringValue, querystringSetting.Value); + case QuerystringSettingMatch.DoesNotMatchRegex: + return !MatchesRegex(querystringValue, querystringSetting.Value); + default: + return false; + } + } + + private static Comparison GetComparison(QuerystringSettingMatch settingMatch) + { + switch (settingMatch) + { + case QuerystringSettingMatch.GreaterThanValue: + return Comparison.GreaterThan; + case QuerystringSettingMatch.GreaterThanOrEqualToValue: + return Comparison.GreaterThanOrEqual; + case QuerystringSettingMatch.LessThanValue: + return Comparison.LessThan; + case QuerystringSettingMatch.LessThanOrEqualToValue: + return Comparison.LessThanOrEqual; + default: + throw new ArgumentException("Setting supplied does not match a comparison type", nameof(settingMatch)); + } + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Criteria/Querystring/QuerystringSetting.cs b/PersonalisationGroups/Criteria/Querystring/QuerystringSetting.cs new file mode 100644 index 0000000..46e3523 --- /dev/null +++ b/PersonalisationGroups/Criteria/Querystring/QuerystringSetting.cs @@ -0,0 +1,24 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.Querystring; + +public enum QuerystringSettingMatch +{ + MatchesValue, + DoesNotMatchValue, + ContainsValue, + DoesNotContainValue, + GreaterThanValue, + GreaterThanOrEqualToValue, + LessThanValue, + LessThanOrEqualToValue, + MatchesRegex, + DoesNotMatchRegex, +} + +public class QuerystringSetting +{ + public required string Key { get; set; } + + public required QuerystringSettingMatch Match { get; set; } + + public required string Value { get; set; } +} \ No newline at end of file diff --git a/PersonalisationGroups/Criteria/Referral/ReferralPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/Referral/ReferralPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..0760883 --- /dev/null +++ b/PersonalisationGroups/Criteria/Referral/ReferralPersonalisationGroupCriteria.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.Referrer; +using System; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.Referral; + +/// +/// Implements a personalisation group criteria based on the presence, absence or value of a session key +/// +public class ReferralPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IReferrerProvider _referrerProvider; + + public ReferralPersonalisationGroupCriteria(IReferrerProvider referrerProvider) + { + _referrerProvider = referrerProvider; + } + + public string Name => "Referral"; + + public string Alias => "referral"; + + public string Description => "Matches visitor with a referral URL"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + ReferralSetting referralSetting; + try + { + referralSetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var referrer = _referrerProvider.GetReferrer(); + switch (referralSetting.Match) + { + case ReferralSettingMatch.MatchesValue: + return MatchesValue(referrer, referralSetting.Value); + case ReferralSettingMatch.DoesNotMatchValue: + return !MatchesValue(referrer, referralSetting.Value); + case ReferralSettingMatch.ContainsValue: + return ContainsValue(referrer, referralSetting.Value); + case ReferralSettingMatch.DoesNotContainValue: + return !ContainsValue(referrer, referralSetting.Value); + default: + return false; + } + } +} diff --git a/PersonalisationGroups/Criteria/Referral/ReferralSetting.cs b/PersonalisationGroups/Criteria/Referral/ReferralSetting.cs new file mode 100644 index 0000000..2ef3ede --- /dev/null +++ b/PersonalisationGroups/Criteria/Referral/ReferralSetting.cs @@ -0,0 +1,16 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.Referral; + +public enum ReferralSettingMatch +{ + MatchesValue, + DoesNotMatchValue, + ContainsValue, + DoesNotContainValue, +} + +public class ReferralSetting +{ + public required ReferralSettingMatch Match { get; set; } + + public required string Value { get; set; } +} diff --git a/PersonalisationGroups/Criteria/Region/RegionPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/Region/RegionPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..ab830f8 --- /dev/null +++ b/PersonalisationGroups/Criteria/Region/RegionPersonalisationGroupCriteria.cs @@ -0,0 +1,86 @@ +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; +using Our.Umbraco.PersonalisationGroups.Providers.Ip; +using System; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.Region; + +/// +/// Implements a personalisation group criteria based on the country derived from the vistor's IP address +/// +public class RegionPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IIpProvider _ipProvider; + private readonly IGeoLocationProvider _geoLocationProvider; + + public RegionPersonalisationGroupCriteria(IIpProvider ipProvider, IGeoLocationProvider geoLocationProvider) + { + _ipProvider = ipProvider; + _geoLocationProvider = geoLocationProvider; + } + + public string Name => "Region"; + + public string Alias => "region"; + + public string Description => "Matches visitor region derived from their IP address to a given list of regions"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + RegionSetting regionSetting; + try + { + regionSetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + var ip = _ipProvider.GetIp(); + if (!string.IsNullOrEmpty(ip)) + { + var country = _geoLocationProvider.GetCountryFromIp(ip); + if (country != null) + { + if (regionSetting.Match == GeoLocationSettingMatch.CouldNotBeLocated) + { + // We can't locate, so return false. + return false; + } + + var matchedCountry = string.Equals(regionSetting.CountryCode, country.Code, StringComparison.InvariantCultureIgnoreCase); + var matchedRegion = false; + if (matchedCountry) + { + var region = _geoLocationProvider.GetRegionFromIp(ip); + if (region != null) + { + matchedRegion = regionSetting.Names + .Intersect(region.GetAllNames(), StringComparer.OrdinalIgnoreCase) + .Any(); + } + } + + switch (regionSetting.Match) + { + case GeoLocationSettingMatch.IsLocatedIn: + return matchedRegion; + case GeoLocationSettingMatch.IsNotLocatedIn: + return !matchedRegion; + default: + return false; + } + } + } + + return regionSetting.Match == GeoLocationSettingMatch.CouldNotBeLocated; + } +} diff --git a/PersonalisationGroups/Criteria/Region/RegionSetting.cs b/PersonalisationGroups/Criteria/Region/RegionSetting.cs new file mode 100644 index 0000000..d8a4c90 --- /dev/null +++ b/PersonalisationGroups/Criteria/Region/RegionSetting.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.Region; + +public class RegionSetting +{ + public required GeoLocationSettingMatch Match { get; set; } + + public required string CountryCode { get; set; } + + public IEnumerable Names { get; set; } = Enumerable.Empty(); +} diff --git a/PersonalisationGroups/Criteria/Session/SessionPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/Session/SessionPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..5a3d58b --- /dev/null +++ b/PersonalisationGroups/Criteria/Session/SessionPersonalisationGroupCriteria.cs @@ -0,0 +1,97 @@ +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.Session; +using System; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.Session; + +/// +/// Implements a personalisation group criteria based on the presence, absence or value of a session key +/// +public class SessionPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly ISessionProvider _sessionProvider; + + public SessionPersonalisationGroupCriteria(ISessionProvider sessionProvider) + { + _sessionProvider = sessionProvider; + } + + public string Name => "Session"; + + public string Alias => "session"; + + public string Description => "Matches visitor session with the presence, absence or value of a session key"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + SessionSetting sessionSetting; + try + { + sessionSetting = JsonConvert.DeserializeObject(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + + if (string.IsNullOrEmpty(sessionSetting.Key)) + { + throw new ArgumentNullException("key", "Session key not set"); + } + + var keyExists = _sessionProvider.KeyExists(sessionSetting.Key); + var value = string.Empty; + if (keyExists) + { + value = _sessionProvider.GetValue(sessionSetting.Key); + } + + switch (sessionSetting.Match) + { + case SessionSettingMatch.Exists: + return keyExists; + case SessionSettingMatch.DoesNotExist: + return !keyExists; + case SessionSettingMatch.MatchesValue: + return keyExists && MatchesValue(value, sessionSetting.Value); + case SessionSettingMatch.ContainsValue: + return keyExists && ContainsValue(value, sessionSetting.Value); + case SessionSettingMatch.GreaterThanValue: + case SessionSettingMatch.GreaterThanOrEqualToValue: + case SessionSettingMatch.LessThanValue: + case SessionSettingMatch.LessThanOrEqualToValue: + return keyExists && + CompareValues(value, sessionSetting.Value, GetComparison(sessionSetting.Match)); + case SessionSettingMatch.MatchesRegex: + return keyExists && MatchesRegex(value, sessionSetting.Value); + case SessionSettingMatch.DoesNotMatchRegex: + return !keyExists || !MatchesRegex(value, sessionSetting.Value); + + default: + return false; + } + } + + private static Comparison GetComparison(SessionSettingMatch settingMatch) + { + switch (settingMatch) + { + case SessionSettingMatch.GreaterThanValue: + return Comparison.GreaterThan; + case SessionSettingMatch.GreaterThanOrEqualToValue: + return Comparison.GreaterThanOrEqual; + case SessionSettingMatch.LessThanValue: + return Comparison.LessThan; + case SessionSettingMatch.LessThanOrEqualToValue: + return Comparison.LessThanOrEqual; + default: + throw new ArgumentException("Setting supplied does not match a comparison type", nameof(settingMatch)); + } + } +} diff --git a/PersonalisationGroups/Criteria/Session/SessionSetting.cs b/PersonalisationGroups/Criteria/Session/SessionSetting.cs new file mode 100644 index 0000000..08e487c --- /dev/null +++ b/PersonalisationGroups/Criteria/Session/SessionSetting.cs @@ -0,0 +1,24 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.Session; + +public enum SessionSettingMatch +{ + Exists, + DoesNotExist, + MatchesValue, + ContainsValue, + GreaterThanValue, + GreaterThanOrEqualToValue, + LessThanValue, + LessThanOrEqualToValue, + MatchesRegex, + DoesNotMatchRegex, +} + +public class SessionSetting +{ + public required string Key { get; set; } + + public required SessionSettingMatch Match { get; set; } + + public required string Value { get; set; } +} diff --git a/PersonalisationGroups/Criteria/TimeOfDay/TimeOfDayPersonalisationGroupCriteria.cs b/PersonalisationGroups/Criteria/TimeOfDay/TimeOfDayPersonalisationGroupCriteria.cs new file mode 100644 index 0000000..b1a0700 --- /dev/null +++ b/PersonalisationGroups/Criteria/TimeOfDay/TimeOfDayPersonalisationGroupCriteria.cs @@ -0,0 +1,47 @@ +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Providers.DateTime; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Criteria.TimeOfDay; + +/// +/// Implements a personalisation group criteria based on the time of the day +/// +public class TimeOfDayPersonalisationGroupCriteria : PersonalisationGroupCriteriaBase, IPersonalisationGroupCriteria +{ + private readonly IDateTimeProvider _dateTimeProvider; + + public TimeOfDayPersonalisationGroupCriteria(IDateTimeProvider dateTimeProvider) + { + _dateTimeProvider = dateTimeProvider; + } + + public string Name => "Time of day"; + + public string Alias => "timeOfDay"; + + public string Description => "Matches visitor session with defined times of the day"; + + public bool MatchesVisitor(string definition) + { + if (string.IsNullOrEmpty(definition)) + { + throw new ArgumentNullException(nameof(definition)); + } + + try + { + var definedTimesOfDay = JsonConvert.DeserializeObject>(definition) + ?? throw new InvalidOperationException($"Could not deserialize JSON: {definition}"); + var now = int.Parse(_dateTimeProvider.GetCurrentDateTime().ToString("HHmm")); + return definedTimesOfDay + .Any(x => x.From <= now && x.To >= now); + } + catch (JsonReaderException) + { + throw new ArgumentException($"Provided definition is not valid JSON: {definition}"); + } + } +} diff --git a/PersonalisationGroups/Criteria/TimeOfDay/TimeOfDaySetting.cs b/PersonalisationGroups/Criteria/TimeOfDay/TimeOfDaySetting.cs new file mode 100644 index 0000000..d311350 --- /dev/null +++ b/PersonalisationGroups/Criteria/TimeOfDay/TimeOfDaySetting.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Criteria.TimeOfDay; + +public class TimeOfDaySetting +{ + public int From { get; set; } + + public int To { get; set; } +} diff --git a/PersonalisationGroups/Extensions/AssemblyExtensions.cs b/PersonalisationGroups/Extensions/AssemblyExtensions.cs new file mode 100644 index 0000000..d3feec9 --- /dev/null +++ b/PersonalisationGroups/Extensions/AssemblyExtensions.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Our.Umbraco.PersonalisationGroups.Extensions; + +/// +/// Provides extension methods to Assembly +/// +public static class AssemblyExtensions +{ + /// + /// Safely loads types from a given assembly avoiding errors if certain ones can't be loaded + /// + /// Instance of an Assembly + /// Enumerable of loadable types + /// + /// For further on this and why needed, see: http://haacked.com/archive/2012/07/23/get-all-types-in-an-assembly.aspx/ + /// + public static IEnumerable GetLoadableTypes(this Assembly assembly) + { + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException e) + { + return e.Types?.Where(t => t != null).Select(t => t!) ?? []; + } + } +} diff --git a/PersonalisationGroups/Extensions/EnumerableExtensions.cs b/PersonalisationGroups/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..541333a --- /dev/null +++ b/PersonalisationGroups/Extensions/EnumerableExtensions.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Extensions; + +public static class EnumerableExtensions +{ + public static bool ContainsAll(this IEnumerable source, IEnumerable other) => !other.Except(source).Any(); + + public static bool ContainsAny(this IEnumerable source, IEnumerable other) => other.Any(source.Contains); +} diff --git a/PersonalisationGroups/Extensions/PublishedElementExtensions.cs b/PersonalisationGroups/Extensions/PublishedElementExtensions.cs new file mode 100644 index 0000000..31f74f1 --- /dev/null +++ b/PersonalisationGroups/Extensions/PublishedElementExtensions.cs @@ -0,0 +1,104 @@ +using Our.Umbraco.PersonalisationGroups.Services; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Web.Common; + +namespace Umbraco.Extensions; + +/// +/// Provides extension methods to IPublishedContent +/// +public static class PublishedContentExtensions +{ + /// + /// Adds an extension method to IPublishedContent to determine if the content item should be shown to the current site + /// visitor, based on the personalisation groups associated with it. + /// + /// Instance of IPublishedContent + /// The group matching service + /// Indicates the response to return if groups cannot be found on the content + /// True if content should be shown to visitor + public static bool ShowToVisitor(this IPublishedElement content, IGroupMatchingService groupMatchingService, bool showIfNoGroupsDefined = true) + { + var pickedGroups = groupMatchingService.GetPickedGroups(content); + return ShowToVisitor(groupMatchingService, pickedGroups, showIfNoGroupsDefined); + } + + /// + /// Adds an extension method to IPublishedContent to score the content item for the current site + /// visitor, based on the personalisation groups associated with it. + /// + /// Instance of IPublishedContent + /// The group matching service + /// True if content should be shown to visitor + public static int ScoreForVisitor(this IPublishedElement content, IGroupMatchingService groupMatchingService) + { + var pickedGroups = groupMatchingService.GetPickedGroups(content); + return ScoreForVisitor(groupMatchingService, pickedGroups); + } + + /// + /// Adds an extension method to UmbracoHelper to determine if the content item should be shown to the current site + /// visitor, based on the personalisation groups associated with the Ids passed into the method + /// + /// Instance of UmbracoHelper + /// The group matching service + /// List of group Ids + /// Indicates the response to return if groups cannot be found on the content + /// True if content should be shown to visitor + public static bool ShowToVisitor(this UmbracoHelper umbraco, IGroupMatchingService groupMatchingService, IEnumerable groupIds, bool showIfNoGroupsDefined = true) + { + var groups = umbraco.Content(groupIds).ToList(); + return ShowToVisitor(groupMatchingService, groups, showIfNoGroupsDefined); + } + + /// + /// Adds an extension method to UmbracoHelper to score the content item for the current site + /// visitor, based on the personalisation groups associated with the Ids passed into the method + /// + /// Instance of UmbracoHelper + /// The group matching service + /// List of group Ids + /// True if content should be shown to visitor + public static int ScoreForVisitor(this UmbracoHelper umbraco, IGroupMatchingService groupMatchingService, IEnumerable groupIds) + { + var groups = umbraco.Content(groupIds).ToList(); + return ScoreForVisitor(groupMatchingService, groups); + } + + /// + /// Determines if the content item should be shown to the current site visitor, based on the personalisation groups associated with it. + /// + /// The group matching service + /// List of IPublishedContent items that are the groups you want to check against. + /// Indicates the response to return if groups cannot be found on the content + /// True if content should be shown to visitor + private static bool ShowToVisitor(IGroupMatchingService groupMatchingService, IList pickedGroups, bool showIfNoGroupsDefined = true) + { + if (!pickedGroups.Any()) + { + // No personalisation groups picked or no property for picker, so we return the provided default + return showIfNoGroupsDefined; + } + + return groupMatchingService.MatchGroups(pickedGroups); + } + + /// + /// Scores the content item for the current site visitor, based on the personalisation groups associated with it. + /// + /// The group matching service + /// List of IPublishedContent items that are the groups you want to check against. + /// True if content should be shown to visitor + private static int ScoreForVisitor(IGroupMatchingService groupMatchingService, IList pickedGroups) + { + if (!pickedGroups.Any()) + { + // No personalisation groups picked or no property for picker, so we score zero + return 0; + } + + return groupMatchingService.ScoreGroups(pickedGroups); + } + } diff --git a/PersonalisationGroups/Extensions/StringExtensions.cs b/PersonalisationGroups/Extensions/StringExtensions.cs new file mode 100644 index 0000000..9d1bf43 --- /dev/null +++ b/PersonalisationGroups/Extensions/StringExtensions.cs @@ -0,0 +1,52 @@ +using System; + +namespace Our.Umbraco.PersonalisationGroups.Extensions; + +public static class StringExtensions +{ + public static bool InvariantEquals(this string compare, string compareTo) => compare.Equals(compareTo, StringComparison.InvariantCultureIgnoreCase); + + public static bool InvariantEndsWith(this string compare, string compareTo) => compare.EndsWith(compareTo, StringComparison.InvariantCultureIgnoreCase); + + public static string TrimStart(this string value, string forRemoving) + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + if (string.IsNullOrEmpty(forRemoving)) + { + return value; + } + + while (value.StartsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) + { + value = value.Substring(forRemoving.Length); + } + + return value; + } + + public static string TrimEnd(this string value, string forRemoving) + { + if (string.IsNullOrEmpty(value)) + { + return value; + } + + if (string.IsNullOrEmpty(forRemoving)) + { + return value; + } + + while (value.EndsWith(forRemoving, StringComparison.InvariantCultureIgnoreCase)) + { + value = value.Remove(value.LastIndexOf(forRemoving, StringComparison.InvariantCultureIgnoreCase)); + } + + return value; + } + + public static string[] SplitByNewLine(this string text, StringSplitOptions options) => text.Split(new[] { "\r\n", "\r", "\n" }, options); +} diff --git a/PersonalisationGroups/Extensions/UmbracoHelperExtensions.cs b/PersonalisationGroups/Extensions/UmbracoHelperExtensions.cs new file mode 100644 index 0000000..1cfe3a4 --- /dev/null +++ b/PersonalisationGroups/Extensions/UmbracoHelperExtensions.cs @@ -0,0 +1,168 @@ +using Our.Umbraco.PersonalisationGroups; +using Our.Umbraco.PersonalisationGroups.Criteria; +using Our.Umbraco.PersonalisationGroups.Services; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Web.Common; + +namespace Umbraco.Extensions; + +/// +/// Provides extension methods to UmbracoHelper +/// +public static class UmbracoHelperExtensions +{ + /// + /// Adds an extension method to UmbracoHelper to determine if the current site + /// visitor matches a single personalisation group. + /// + /// Instance of UmbracoHelper + /// The group matching service. + /// Name of group node to match + /// True if visitor matches group + public static bool MatchesGroup(this UmbracoHelper helper, IGroupMatchingService groupMatchingService, string groupName) + { + return MatchesGroups(helper, groupMatchingService, new string[] { groupName }, PersonalisationGroupDefinitionMatch.Any); + } + + /// + /// Adds an extension method to UmbracoHelper to determine if the current site + /// visitor matches any of a set of personalisation groups. + /// + /// Instance of UmbracoHelper + /// The group matching service. + /// Names of group nodes to match + /// True if visitor matches any group + public static bool MatchesAnyGroup(this UmbracoHelper helper, IGroupMatchingService groupMatchingService, string[] groupNames) + { + return MatchesGroups(helper, groupMatchingService, groupNames, PersonalisationGroupDefinitionMatch.Any); + } + + /// + /// Adds an extension method to UmbracoHelper to determine if the current site + /// visitor matches all of a set of personalisation groups. + /// + /// Instance of UmbracoHelper + /// The group matching service. + /// Names of group nodes to match + /// True if visitor matches all groups + public static bool MatchesAllGroups(this UmbracoHelper helper, IGroupMatchingService groupMatchingService, string[] groupNames) + { + return MatchesGroups(helper, groupMatchingService, groupNames, PersonalisationGroupDefinitionMatch.All); + } + + private static bool MatchesGroups(this UmbracoHelper helper, IGroupMatchingService groupMatchingService, string[] groupNames, PersonalisationGroupDefinitionMatch matchType) + { + var groupsRootFolder = GetGroupsRootFolder(helper); + if (groupsRootFolder == null) + { + return false; + } + + var groups = GetGroups(groupsRootFolder); + return groupMatchingService.MatchGroupsByName(groupNames, groups, matchType); + } + + private static IPublishedContent? GetGroupsRootFolder(UmbracoHelper helper) + { + return helper.ContentAtRoot() + .FirstOrDefault(x => x.IsDocumentType(AppConstants.DocumentTypeAliases.PersonalisationGroupsFolder)); + } + + private static IList GetGroups(IPublishedContent groupsRootFolder) + { + return groupsRootFolder.Descendants() + .Where(x => x.IsDocumentType(AppConstants.DocumentTypeAliases.PersonalisationGroup)) + .ToList(); + } + + /// + /// Adds an extension method to UmbracoHelper to calculate a hash for the current visitor for all visitor groups + /// + /// Instance of UmbracoHelper + /// The group matching cache + /// Provides access to the runtime cache + /// Id of root node for the personalisation groups + /// Identifier for the user to use in the cache key (likely the session Id) + /// Length of time in seconds to cache the generated personalisation group hash for the visitor + /// Has for the visitor for all groups + public static string GetPersonalisationGroupsHashForVisitor( + this UmbracoHelper helper, + IGroupMatchingService groupMatchingService, + AppCaches appCaches, + Guid personalisationGroupsRootNodeId, + string cacheUserIdentifier, + int cacheForSeconds) + { + var personalisationGroupsRootNode = helper.Content(personalisationGroupsRootNodeId) + ?? throw new InvalidOperationException($"Could not retrieve content for group root node {personalisationGroupsRootNodeId}"); + if (!personalisationGroupsRootNode.IsDocumentType(AppConstants.DocumentTypeAliases.PersonalisationGroupsFolder)) + { + throw new InvalidOperationException( + $"The personalisation groups hash for a visitor can only be calculated for a root node of type {AppConstants.DocumentTypeAliases.PersonalisationGroupsFolder}"); + } + + return GetPersonalisationGroupsHashForVisitor(helper, groupMatchingService, appCaches, personalisationGroupsRootNode, cacheUserIdentifier, cacheForSeconds); + } + + /// + /// Adds an extension method to UmbracoHelper to calculate a hash for the current visitor for all visitor groups + /// + /// Instance of UmbracoHelper + /// The group matching cache + /// Provides access to the runtime cache + /// Id of root node for the personalisation groups + /// Identifier for the user to use in the cache key (likely the session Id) + /// Length of time in seconds to cache the generated personalisation group hash for the visitor + /// Has for the visitor for all groups + public static string GetPersonalisationGroupsHashForVisitor( + this UmbracoHelper helper, + IGroupMatchingService groupMatchingService, + AppCaches appCaches, + int personalisationGroupsRootNodeId, + string cacheUserIdentifier, + int cacheForSeconds) + { + var personalisationGroupsRootNode = helper.Content(personalisationGroupsRootNodeId) + ?? throw new InvalidOperationException($"Could not retrieve content for group root node {personalisationGroupsRootNodeId}"); + if (!personalisationGroupsRootNode.IsDocumentType(AppConstants.DocumentTypeAliases.PersonalisationGroupsFolder)) + { + throw new InvalidOperationException( + $"The personalisation groups hash for a visitor can only be calculated for a root node of type {AppConstants.DocumentTypeAliases.PersonalisationGroupsFolder}"); + } + + return GetPersonalisationGroupsHashForVisitor(helper, groupMatchingService, appCaches, personalisationGroupsRootNode, cacheUserIdentifier, cacheForSeconds); + } + + /// + /// Adds an extension method to UmbracoHelper to calculate a hash for the current visitor for all visitor groups + /// + /// Instance of UmbracoHelper + /// The group matching cache + /// Provides access to the runtime cache + /// Root node for the personalisation groups + /// Identifier for the user to use in the cache key (likely the session Id) + /// Length of time in seconds to cache the generated personalisation group hash for the visitor + /// Has for the visitor for all groups + public static string GetPersonalisationGroupsHashForVisitor( + this UmbracoHelper helper, + IGroupMatchingService groupMatchingService, + AppCaches appCaches, + IPublishedContent personalisationGroupsRootNode, + string cacheUserIdentifier, + int cacheForSeconds) + { + if (personalisationGroupsRootNode == null) + { + throw new ArgumentNullException(nameof(personalisationGroupsRootNode)); + } + + var cacheKey = $"{cacheUserIdentifier}-{AppConstants.CacheKeys.PersonalisationGroupsVisitorHash}"; + return appCaches.RuntimeCache.GetCacheItem(cacheKey, + () => groupMatchingService.CreatePersonalisationGroupsHashForVisitor(personalisationGroupsRootNode), + timeout: TimeSpan.FromSeconds(cacheForSeconds)) ?? string.Empty; + } +} diff --git a/PersonalisationGroups/Middleware/TrackUserActivityMiddleware.cs b/PersonalisationGroups/Middleware/TrackUserActivityMiddleware.cs new file mode 100644 index 0000000..bd1d32b --- /dev/null +++ b/PersonalisationGroups/Middleware/TrackUserActivityMiddleware.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Http; +using Our.Umbraco.PersonalisationGroups.Services; +using System; +using System.Threading.Tasks; +using Umbraco.Cms.Core.Web; +using Umbraco.Extensions; + +namespace Our.Umbraco.PersonalisationGroups.Middleware; + +internal class TrackUserActivityMiddleware : IMiddleware +{ + private readonly IUmbracoContextAccessor _umbracoContextAccessor; + private readonly IUserActivityTracker _userActivityTracker; + + public TrackUserActivityMiddleware(IUmbracoContextAccessor umbracoContextAccessor, IUserActivityTracker userActivityTracker) + { + _umbracoContextAccessor = umbracoContextAccessor; + _userActivityTracker = userActivityTracker; + } + + public async Task InvokeAsync(HttpContext context, RequestDelegate next) + { + if (!_umbracoContextAccessor.TryGetUmbracoContext(out var umbracoContext)) + { + await next(context); + return ; + } + + var isFrontEndRequest = umbracoContext.IsFrontEndUmbracoRequest(); + if (!isFrontEndRequest) + { + await next(context); + return; + } + + var pageKey = umbracoContext.PublishedRequest?.PublishedContent?.Key; + if (pageKey == null) + { + await next(context); + return; + } + + _userActivityTracker.TrackPageView(pageKey.Value); + + _userActivityTracker.TrackSession(); + + await next(context); + } +} diff --git a/PersonalisationGroups/Migrations/PersonalisationGroupsPackageMigrationPlan.cs b/PersonalisationGroups/Migrations/PersonalisationGroupsPackageMigrationPlan.cs new file mode 100644 index 0000000..01fa6ce --- /dev/null +++ b/PersonalisationGroups/Migrations/PersonalisationGroupsPackageMigrationPlan.cs @@ -0,0 +1,11 @@ +using Umbraco.Cms.Infrastructure.Packaging; + +namespace Our.Umbraco.PersonalisationGroups.Migrations; + +public class PersonalisationGroupsPackageMigrationPlan : AutomaticPackageMigrationPlan +{ + public PersonalisationGroupsPackageMigrationPlan() + : base(AppConstants.PackageName) + { + } +} diff --git a/PersonalisationGroups/Migrations/package.xml b/PersonalisationGroups/Migrations/package.xml new file mode 100644 index 0000000..9746be0 --- /dev/null +++ b/PersonalisationGroups/Migrations/package.xml @@ -0,0 +1,95 @@ + + + + + Personalisation Groups + + + + + + + + + + + Personalisation Group + personalisationGroup + a960545f-a794-412d-996a-8f9a7da522c4 + icon-operator color-green + folder.png + + False + + False + Nothing + + + + + + + + Group definition + definition + 167df64e-ad8a-4af1-b0c6-a39dfe21820d + PersonalisationGroups.GroupDefinition + 56f6775d-e85f-4863-819d-fbf890bc6666 + Settings + 0 + False + False + + + Nothing + + + + + 56 + 7b144c1c-7609-4d50-83a7-faa8e4decc34 + Group + Settings + settings + 0 + + + + + + + Personalisation Groups Folder + personalisationGroupsFolder + 46b8efcf-a31d-43bf-b5b6-bea0e42bed41 + icon-folder color-green + folder.png + + True + + False + Nothing + + + + + + personalisationGroup + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/PersonalisationGroups/PropertyEditors/PersonalisationGroupDefinitionPropertyEditor.cs b/PersonalisationGroups/PropertyEditors/PersonalisationGroupDefinitionPropertyEditor.cs new file mode 100644 index 0000000..173d05a --- /dev/null +++ b/PersonalisationGroups/PropertyEditors/PersonalisationGroupDefinitionPropertyEditor.cs @@ -0,0 +1,21 @@ +using Umbraco.Cms.Core.PropertyEditors; +using Constants = Our.Umbraco.PersonalisationGroups.AppConstants; + +namespace Our.Umbraco.PersonalisationGroups.PropertyEditors; + +/// +/// Property editor for managing the definition of a personalisation group. +/// +[DataEditor( + alias: Constants.PersonalisationGroupDefinitionPropertyEditorAlias, + //name: "Personalisation group definition", + //view: "/App_Plugins/PersonalisationGroups/personalisation-group-definition.html", + //Icon = "icon-operator", + ValueType = "JSON")] +public class PersonalisationGroupDefinitionPropertyEditor : DataEditor +{ + public PersonalisationGroupDefinitionPropertyEditor(IDataValueEditorFactory dataValueEditorFactory) + : base(dataValueEditorFactory) + { + } +} diff --git a/PersonalisationGroups/PropertyValueConverters/PersonalisationGroupDefinitionPropertyValueConverter.cs b/PersonalisationGroups/PropertyValueConverters/PersonalisationGroupDefinitionPropertyValueConverter.cs new file mode 100644 index 0000000..7971bfa --- /dev/null +++ b/PersonalisationGroups/PropertyValueConverters/PersonalisationGroupDefinitionPropertyValueConverter.cs @@ -0,0 +1,40 @@ +using System; +using Newtonsoft.Json; +using Our.Umbraco.PersonalisationGroups.Criteria; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.PropertyEditors; + +namespace Our.Umbraco.PersonalisationGroups.PropertyValueConverters; + +/// +/// Property converter to convert the saved JSON representation of a personalisation group definition to the +/// strongly typed model. +/// +public class PersonalisationGroupDefinitionPropertyValueConverter : PropertyValueConverterBase +{ + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) + => typeof(PersonalisationGroupDefinition); + + public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) + => PropertyCacheLevel.Element; + + public override bool IsConverter(IPublishedPropertyType propertyType) + { + return propertyType.EditorAlias.Equals(AppConstants.PersonalisationGroupDefinitionPropertyEditorAlias); + } + + public override object? ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object? source, bool preview) + { + return source; + } + + public override object? ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object? inter, bool preview) + { + if (inter == null) + { + return null; + } + + return JsonConvert.DeserializeObject(inter.ToString()!); + } +} \ No newline at end of file diff --git a/PersonalisationGroups/Providers/AuthenticationStatus/HttpContextAuthenticationStatusProvider.cs b/PersonalisationGroups/Providers/AuthenticationStatus/HttpContextAuthenticationStatusProvider.cs new file mode 100644 index 0000000..e755355 --- /dev/null +++ b/PersonalisationGroups/Providers/AuthenticationStatus/HttpContextAuthenticationStatusProvider.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Http; + +namespace Our.Umbraco.PersonalisationGroups.Providers.AuthenticationStatus; + +public class HttpContextAuthenticationStatusProvider : IAuthenticationStatusProvider +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpContextAuthenticationStatusProvider(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public bool IsAuthenticated() + { + return _httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated ?? false; + } +} diff --git a/PersonalisationGroups/Providers/AuthenticationStatus/IAuthenticationStatusProvider.cs b/PersonalisationGroups/Providers/AuthenticationStatus/IAuthenticationStatusProvider.cs new file mode 100644 index 0000000..07b87ba --- /dev/null +++ b/PersonalisationGroups/Providers/AuthenticationStatus/IAuthenticationStatusProvider.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.AuthenticationStatus; + +public interface IAuthenticationStatusProvider +{ + bool IsAuthenticated(); +} diff --git a/PersonalisationGroups/Providers/Cookie/HttpContextCookieProvider.cs b/PersonalisationGroups/Providers/Cookie/HttpContextCookieProvider.cs new file mode 100644 index 0000000..ad90a55 --- /dev/null +++ b/PersonalisationGroups/Providers/Cookie/HttpContextCookieProvider.cs @@ -0,0 +1,88 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; +using System; +using Umbraco.Cms.Core.Web; + +namespace Our.Umbraco.PersonalisationGroups.Providers.Cookie; + +public class HttpContextCookieProvider : ICookieProvider +{ + private readonly PersonalisationGroupsConfig _config; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ISessionManager _sessionManager; + + public HttpContextCookieProvider(IOptions config, IHttpContextAccessor httpContextAccessor, ISessionManager sessionManager) + { + _config = config.Value; + _httpContextAccessor = httpContextAccessor; + _sessionManager = sessionManager; + } + + public bool CookieExists(string key) + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) + { + return false; + } + + return !_config.DisableHttpContextItemsUseInCookieOperations && + httpContext.Items.ContainsKey($"personalisationGroups.cookie.{key}") + || httpContext.Request.Cookies[key] != null; + } + + public string? GetCookieValue(string key) + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) + { + return null; + } + + if (!_config.DisableHttpContextItemsUseInCookieOperations && + httpContext.Items.ContainsKey($"personalisationGroups.cookie.{key}")) + { + return httpContext.Items[$"personalisationGroups.cookie.{key}"]?.ToString(); + } + + return httpContext.Request.Cookies[key]; + } + + public void SetCookie(string key, string value, System.DateTime? expires = null, bool httpOnly = true) + { + if (AreCookiesDeclined()) + { + return; + } + + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) + { + return; + } + + if (!_config.DisableHttpContextItemsUseInCookieOperations) + { + httpContext.Items[$"personalisationGroups.cookie.{key}"] = value; + } + + var cookieOptions = new CookieOptions + { + Expires = expires.HasValue ? new DateTimeOffset(expires.Value) : (DateTimeOffset?)null, + HttpOnly = httpOnly, + Secure = true + }; + httpContext.Response.Cookies.Append(key, value, cookieOptions); + } + + public void DeleteCookie(string key) => _httpContextAccessor.HttpContext?.Response.Cookies.Delete(key); + + private bool AreCookiesDeclined() + { + // Cookies can be declined by a solution developer either by setting a cookie or session variable. + // If either of these exist, we shouldn't write any cookies. + return _httpContextAccessor.HttpContext?.Request.Cookies[_config.CookieKeyForTrackingCookiesDeclined] != null || + _sessionManager.GetSessionValue(_config.SessionKeyForTrackingCookiesDeclined) != null; + } +} diff --git a/PersonalisationGroups/Providers/Cookie/ICookieProvider.cs b/PersonalisationGroups/Providers/Cookie/ICookieProvider.cs new file mode 100644 index 0000000..15989eb --- /dev/null +++ b/PersonalisationGroups/Providers/Cookie/ICookieProvider.cs @@ -0,0 +1,12 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.Cookie; + +public interface ICookieProvider +{ + bool CookieExists(string key); + + string? GetCookieValue(string key); + + void SetCookie(string key, string value, System.DateTime? expires = null, bool httpOnly = true); + + void DeleteCookie(string key); +} diff --git a/PersonalisationGroups/Providers/DateTime/DateTimeProvider.cs b/PersonalisationGroups/Providers/DateTime/DateTimeProvider.cs new file mode 100644 index 0000000..26d2694 --- /dev/null +++ b/PersonalisationGroups/Providers/DateTime/DateTimeProvider.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.DateTime; + +public class DateTimeProvider : IDateTimeProvider +{ + public System.DateTime GetCurrentDateTime() => System.DateTime.Now; +} diff --git a/PersonalisationGroups/Providers/DateTime/IDateTimeProvider.cs b/PersonalisationGroups/Providers/DateTime/IDateTimeProvider.cs new file mode 100644 index 0000000..d927924 --- /dev/null +++ b/PersonalisationGroups/Providers/DateTime/IDateTimeProvider.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.DateTime; + +public interface IDateTimeProvider +{ + System.DateTime GetCurrentDateTime(); +} diff --git a/PersonalisationGroups/Providers/GeoLocation/CdnHeaderCountryCodeProvider.cs b/PersonalisationGroups/Providers/GeoLocation/CdnHeaderCountryCodeProvider.cs new file mode 100644 index 0000000..4b0560a --- /dev/null +++ b/PersonalisationGroups/Providers/GeoLocation/CdnHeaderCountryCodeProvider.cs @@ -0,0 +1,24 @@ +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Providers.RequestHeaders; + +namespace Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +public class CdnHeaderCountryCodeProvider : ICountryCodeProvider +{ + private readonly PersonalisationGroupsConfig _config; + private readonly IRequestHeadersProvider _requestHeadersProvider; + + public CdnHeaderCountryCodeProvider(IOptions config, IRequestHeadersProvider requestHeadersProvider) + { + _config = config.Value; + _requestHeadersProvider = requestHeadersProvider; + } + + public string? GetCountryCode() + { + var headers = _requestHeadersProvider.GetHeaders(); + var headerName = _config.CdnCountryCodeHttpHeaderName; + return headers?[headerName]; + } +} diff --git a/PersonalisationGroups/Providers/GeoLocation/Continent.cs b/PersonalisationGroups/Providers/GeoLocation/Continent.cs new file mode 100644 index 0000000..2625401 --- /dev/null +++ b/PersonalisationGroups/Providers/GeoLocation/Continent.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +public class Continent +{ + public required string Code { get; set; } + + public required string Name { get; set; } +} diff --git a/PersonalisationGroups/Providers/GeoLocation/Country.cs b/PersonalisationGroups/Providers/GeoLocation/Country.cs new file mode 100644 index 0000000..5c11019 --- /dev/null +++ b/PersonalisationGroups/Providers/GeoLocation/Country.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +public class Country +{ + public required string Code { get; set; } + + public required string Name { get; set; } +} diff --git a/PersonalisationGroups/Providers/GeoLocation/CountryCodeProvider.cs b/PersonalisationGroups/Providers/GeoLocation/CountryCodeProvider.cs new file mode 100644 index 0000000..edee494 --- /dev/null +++ b/PersonalisationGroups/Providers/GeoLocation/CountryCodeProvider.cs @@ -0,0 +1,7 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +public enum CountryCodeProvider +{ + MaxMindDatabase, + CdnHeader +} diff --git a/PersonalisationGroups/Providers/GeoLocation/ICountryCodeProvider.cs b/PersonalisationGroups/Providers/GeoLocation/ICountryCodeProvider.cs new file mode 100644 index 0000000..3e2b039 --- /dev/null +++ b/PersonalisationGroups/Providers/GeoLocation/ICountryCodeProvider.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +public interface ICountryCodeProvider +{ + string? GetCountryCode(); +} diff --git a/PersonalisationGroups/Providers/GeoLocation/IGeoLocationProvider.cs b/PersonalisationGroups/Providers/GeoLocation/IGeoLocationProvider.cs new file mode 100644 index 0000000..0d7aae7 --- /dev/null +++ b/PersonalisationGroups/Providers/GeoLocation/IGeoLocationProvider.cs @@ -0,0 +1,10 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +public interface IGeoLocationProvider +{ + Continent? GetContinentFromIp(string ip); + + Country? GetCountryFromIp(string ip); + + Region? GetRegionFromIp(string ip); +} diff --git a/PersonalisationGroups/Providers/GeoLocation/MaxMindCountryCodeFromIpProvider.cs b/PersonalisationGroups/Providers/GeoLocation/MaxMindCountryCodeFromIpProvider.cs new file mode 100644 index 0000000..68c2b47 --- /dev/null +++ b/PersonalisationGroups/Providers/GeoLocation/MaxMindCountryCodeFromIpProvider.cs @@ -0,0 +1,27 @@ +using Our.Umbraco.PersonalisationGroups.Providers.Ip; + +namespace Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +public class MaxMindCountryCodeFromIpProvider : ICountryCodeProvider +{ + private readonly IIpProvider _ipProvider; + private readonly IGeoLocationProvider _geoLocationProvider; + + public MaxMindCountryCodeFromIpProvider(IIpProvider ipProvider, IGeoLocationProvider geoLocationProvider) + { + _ipProvider = ipProvider; + _geoLocationProvider = geoLocationProvider; + } + + public string? GetCountryCode() + { + var ip = _ipProvider.GetIp(); + if (string.IsNullOrEmpty(ip)) + { + return string.Empty; + } + + var country = _geoLocationProvider.GetCountryFromIp(ip); + return country?.Code; + } +} diff --git a/PersonalisationGroups/Providers/GeoLocation/MaxMindGeoLocationProvider.cs b/PersonalisationGroups/Providers/GeoLocation/MaxMindGeoLocationProvider.cs new file mode 100644 index 0000000..c46d172 --- /dev/null +++ b/PersonalisationGroups/Providers/GeoLocation/MaxMindGeoLocationProvider.cs @@ -0,0 +1,171 @@ +using MaxMind.GeoIP2; +using MaxMind.GeoIP2.Exceptions; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; +using System; +using System.IO; +using System.Linq; +using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Extensions; + +namespace Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +public class MaxMindGeoLocationProvider : IGeoLocationProvider +{ + private readonly string _pathToCountryDb; + private readonly string _pathToCityDb; + private readonly AppCaches _appCaches; + + public MaxMindGeoLocationProvider(IOptions config, IHostEnvironment hostEnvironment, AppCaches appCaches) + { + _pathToCountryDb = hostEnvironment.MapPathContentRoot(config.Value.GeoLocationCountryDatabasePath); + _pathToCityDb = hostEnvironment.MapPathContentRoot(config.Value.GeoLocationCityDatabasePath); + _appCaches = appCaches; + } + + public Continent? GetContinentFromIp(string ip) + { + var cacheKey = $"PersonalisationGroups_GeoLocation_Continent_{ip}"; + var cachedItem = _appCaches.RuntimeCache.Get(cacheKey, + () => + { + try + { + using (var reader = new DatabaseReader(_pathToCountryDb)) + { + try + { + var response = reader.Country(ip); + return new Continent { Code = response.Continent.Code!, Name = response.Continent.Name!, }; + } + catch (AddressNotFoundException) + { + return null; + } + catch (GeoIP2Exception ex) + { + if (IsInvalidIpException(ex)) + { + return null; + } + + throw; + } + } + } + catch (FileNotFoundException) + { + throw new FileNotFoundException( + $"MaxMind Geolocation database required for locating visitor continent from IP address not found, expected at: {_pathToCountryDb}. The path is derived from either the default ({AppConstants.DefaultGeoLocationCountryDatabasePath}) or can be configured using a relative path in an appSetting with key: \"{nameof(PersonalisationGroupsConfig.GeoLocationCountryDatabasePath)}\"", + _pathToCountryDb); + } + }); + + return cachedItem as Continent; + } + + public Country? GetCountryFromIp(string ip) + { + var cacheKey = $"PersonalisationGroups_GeoLocation_Country_{ip}"; + var cachedItem = _appCaches.RuntimeCache.Get(cacheKey, + () => + { + try + { + using (var reader = new DatabaseReader(_pathToCountryDb)) + { + try + { + var response = reader.Country(ip); + return new Country { Code = response.Country.IsoCode!, Name = response.Country.Name!, }; + } + catch (AddressNotFoundException) + { + return null; + } + catch (GeoIP2Exception ex) + { + if (IsInvalidIpException(ex)) + { + return null; + } + + throw; + } + } + } + catch (FileNotFoundException) + { + throw new FileNotFoundException( + $"MaxMind Geolocation database required for locating visitor country from IP address not found, expected at: {_pathToCountryDb}. The path is derived from either the default ({AppConstants.DefaultGeoLocationCountryDatabasePath}) or can be configured using a relative path in an appSetting with key: \"{nameof(PersonalisationGroupsConfig.GeoLocationCountryDatabasePath)}\"", + _pathToCountryDb); + } + }); + + return cachedItem as Country; + } + + public Region? GetRegionFromIp(string ip) + { + var cacheKey = $"PersonalisationGroups_GeoLocation_Region_{ip}"; + var cachedItem = _appCaches.RuntimeCache.Get(cacheKey, + () => + { + try + { + using (var reader = new DatabaseReader(_pathToCityDb)) + { + try + { + var response = reader.City(ip); + var region = new Region + { + City = response.City.Name ?? string.Empty, + Subdivisions = response.Subdivisions + ?.Select(x => x.Name ?? string.Empty) + .Union(response.Subdivisions + .SelectMany(x => x.Names + .Where(y => !string.IsNullOrEmpty(y.Value)) + .Select(y => y.Value ?? string.Empty))) + .ToArray() ?? Array.Empty(), + Country = new Country + { + Code = response.Country.IsoCode ?? string.Empty, + Name = response.Country.Name ?? string.Empty, + } + }; + + return region; + } + catch (AddressNotFoundException) + { + return null; + } + catch (GeoIP2Exception ex) + { + if (IsInvalidIpException(ex)) + { + return null; + } + + throw; + } + } + } + catch (FileNotFoundException) + { + throw new FileNotFoundException( + $"MaxMind Geolocation database required for locating visitor region from IP address not found, expected at: {_pathToCountryDb}. The path is derived from either the default ({AppConstants.DefaultGeoLocationCountryDatabasePath}) or can be configured using a relative path in an appSetting with key: \"{nameof(PersonalisationGroupsConfig.GeoLocationCityDatabasePath)}\"", + _pathToCountryDb); + } + }); + + return cachedItem as Region; + } + + private static bool IsInvalidIpException(GeoIP2Exception ex) + { + return ex.Message.StartsWith("The specified IP address was incorrectly formatted"); + } +} diff --git a/PersonalisationGroups/Providers/GeoLocation/Region.cs b/PersonalisationGroups/Providers/GeoLocation/Region.cs new file mode 100644 index 0000000..d35855a --- /dev/null +++ b/PersonalisationGroups/Providers/GeoLocation/Region.cs @@ -0,0 +1,23 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; + +using System.Collections.Generic; + +public class Region +{ + public required string City { get; set; } + + public required string[] Subdivisions { get; set; } + + public required Country Country { get; set; } + + public string[] GetAllNames() + { + var names = new List { City }; + if (Subdivisions != null) + { + names.AddRange(Subdivisions); + } + + return names.ToArray(); + } +} diff --git a/PersonalisationGroups/Providers/Host/HttpContextHostProvider.cs b/PersonalisationGroups/Providers/Host/HttpContextHostProvider.cs new file mode 100644 index 0000000..94a08e3 --- /dev/null +++ b/PersonalisationGroups/Providers/Host/HttpContextHostProvider.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Http; + +namespace Our.Umbraco.PersonalisationGroups.Providers.Host; + +public class HttpContextHostProvider : IHostProvider +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpContextHostProvider(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public string? GetHost() + { + return _httpContextAccessor.HttpContext?.Request.Host.Value; + } +} diff --git a/PersonalisationGroups/Providers/Host/IHostProvider.cs b/PersonalisationGroups/Providers/Host/IHostProvider.cs new file mode 100644 index 0000000..b509b86 --- /dev/null +++ b/PersonalisationGroups/Providers/Host/IHostProvider.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.Host; + +public interface IHostProvider +{ + string? GetHost(); +} diff --git a/PersonalisationGroups/Providers/Ip/ClientIpParser.cs b/PersonalisationGroups/Providers/Ip/ClientIpParser.cs new file mode 100644 index 0000000..9acea9a --- /dev/null +++ b/PersonalisationGroups/Providers/Ip/ClientIpParser.cs @@ -0,0 +1,74 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; + +namespace Our.Umbraco.PersonalisationGroups.Providers.Ip; + +public class ClientIpParser +{ + private static readonly IEnumerable HeaderKeys = new[] + { + "CF-Connecting-IP", "HTTP_X_FORWARDED_FOR", "REMOTE_ADDR", + "HTTP_CLIENT_IP", "HTTP_X_FORWARDED", "HTTP_X_CLUSTER_CLIENT_IP", + "HTTP_FORWARDED_FOR", "HTTP_FORWARDED" + }; + + public string? ParseClientIp(HttpContext httpContext) + { + if (httpContext == null) + { + return null; + } + + foreach (var key in HeaderKeys) + { + if (TryParseIpFromHeaders(httpContext.Request.Headers, key, out string? ip)) + { + return ip; + } + } + + return httpContext.Connection.RemoteIpAddress?.ToString(); + } + + private bool TryParseIpFromHeaders(IHeaderDictionary requestHeaders, string key, out string? ip) + { + var value = requestHeaders[key]; + if (value == StringValues.Empty) + { + ip = null; + return false; + } + + // We don't want local ips + if (value.ToString().StartsWith("192.")) + { + ip = null; + return false; + } + + // We might not have a single IP here, as it's possible if the request has passed through multiple proxies, there will be + // additional ones in the header + // If so, the original requesting IP is the first one in a comma+space delimited list + value = value.ToString().Split(new[] { ", " }, StringSplitOptions.None).First(); + ip = RemovePortNumberFromIp(value); + + // Finally, ensure we have a valid IP. + return IsValidIp(ip); + } + + private static string RemovePortNumberFromIp(StringValues ip) + { + if (ip.Contains(":")) + { + ip = ip.ToString().Substring(0, ip.ToString().IndexOf(":", StringComparison.Ordinal)); + } + + return ip.ToString(); + } + + private static bool IsValidIp(string ip) => !string.IsNullOrEmpty(ip) && IPAddress.TryParse(ip, out _); +} diff --git a/PersonalisationGroups/Providers/Ip/HttpContextIpProvider.cs b/PersonalisationGroups/Providers/Ip/HttpContextIpProvider.cs new file mode 100644 index 0000000..c67f476 --- /dev/null +++ b/PersonalisationGroups/Providers/Ip/HttpContextIpProvider.cs @@ -0,0 +1,49 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; + +namespace Our.Umbraco.PersonalisationGroups.Providers.Ip; + +public class HttpContextIpProvider : IIpProvider +{ + private readonly PersonalisationGroupsConfig _config; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly ClientIpParser _clientIpParser; + + public HttpContextIpProvider(IOptions config, IHttpContextAccessor httpContextAccessor, ClientIpParser clientIpParser) + { + _config = config.Value; + _httpContextAccessor = httpContextAccessor; + _clientIpParser = clientIpParser; + } + + public string? GetIp() + { + var ip = GetIpFromHttpContext(); + if (ip == "::1") + { + ip = "127.0.0.1"; + } + + return ip; + } + + private string? GetIpFromHttpContext() + { + // Return a test Ip if we've configured one. + var testIp = _config.TestFixedIp; + if (!string.IsNullOrEmpty(testIp)) + { + return testIp; + } + + // Otherwise retrieve from the HTTP context. + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) + { + return null; + } + + return _clientIpParser.ParseClientIp(httpContext); + } +} diff --git a/PersonalisationGroups/Providers/Ip/IIpProvider.cs b/PersonalisationGroups/Providers/Ip/IIpProvider.cs new file mode 100644 index 0000000..3b47bdc --- /dev/null +++ b/PersonalisationGroups/Providers/Ip/IIpProvider.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.Ip; + +public interface IIpProvider +{ + string? GetIp(); +} diff --git a/PersonalisationGroups/Providers/MemberGroup/IMemberGroupProvider.cs b/PersonalisationGroups/Providers/MemberGroup/IMemberGroupProvider.cs new file mode 100644 index 0000000..d3eaa54 --- /dev/null +++ b/PersonalisationGroups/Providers/MemberGroup/IMemberGroupProvider.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.MemberGroup; + +using System.Collections.Generic; + +public interface IMemberGroupProvider +{ + IEnumerable GetMemberGroups(); +} diff --git a/PersonalisationGroups/Providers/MemberGroup/UmbracoMemberGroupProvider.cs b/PersonalisationGroups/Providers/MemberGroup/UmbracoMemberGroupProvider.cs new file mode 100644 index 0000000..cc5f594 --- /dev/null +++ b/PersonalisationGroups/Providers/MemberGroup/UmbracoMemberGroupProvider.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Http; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Services; + +namespace Our.Umbraco.PersonalisationGroups.Providers.MemberGroup; + +public class UmbracoMemberGroupProvider : IMemberGroupProvider +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IMemberService _memberService; + + public UmbracoMemberGroupProvider(IHttpContextAccessor httpContextAccessor, IMemberService memberService) + { + _httpContextAccessor = httpContextAccessor; + _memberService = memberService; + } + + public IEnumerable GetMemberGroups() + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext == null) + { + return Enumerable.Empty(); + } + + return httpContext.User?.Identity?.IsAuthenticated ?? false + ? GetAuthenticatedMemberGroups() + : Enumerable.Empty(); + } + + private IEnumerable GetAuthenticatedMemberGroups() + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext?.User?.Identity?.Name == null) + { + return Enumerable.Empty(); + } + + var memberGroups = _memberService.GetAllRoles(httpContext.User.Identity.Name); + return memberGroups ?? Enumerable.Empty(); + } +} diff --git a/PersonalisationGroups/Providers/MemberProfileField/IMemberProfileFieldProvider.cs b/PersonalisationGroups/Providers/MemberProfileField/IMemberProfileFieldProvider.cs new file mode 100644 index 0000000..aa46f8f --- /dev/null +++ b/PersonalisationGroups/Providers/MemberProfileField/IMemberProfileFieldProvider.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.MemberProfileField; + +public interface IMemberProfileFieldProvider +{ + string? GetMemberProfileFieldValue(string alias); +} diff --git a/PersonalisationGroups/Providers/MemberProfileField/UmbracoMemberProfileFieldProvider.cs b/PersonalisationGroups/Providers/MemberProfileField/UmbracoMemberProfileFieldProvider.cs new file mode 100644 index 0000000..d9d8e67 --- /dev/null +++ b/PersonalisationGroups/Providers/MemberProfileField/UmbracoMemberProfileFieldProvider.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Http; +using Umbraco.Cms.Core.Services; + +namespace Our.Umbraco.PersonalisationGroups.Providers.MemberProfileField; + +public class UmbracoMemberProfileFieldProvider : IMemberProfileFieldProvider +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IMemberService _memberService; + + public UmbracoMemberProfileFieldProvider(IHttpContextAccessor httpContextAccessor, IMemberService memberService) + { + _httpContextAccessor = httpContextAccessor; + _memberService = memberService; + } + + public string? GetMemberProfileFieldValue(string alias) + { + return _httpContextAccessor.HttpContext?.User?.Identity?.IsAuthenticated ?? false + ? GetAuthenticatedMemberProfileFieldValue(alias) + : null; + } + + private string? GetAuthenticatedMemberProfileFieldValue(string alias) + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext?.User?.Identity?.Name == null) + { + return null; + } + + var member = _memberService.GetByUsername(httpContext.User.Identity.Name); + if (member == null) + { + return null; + } + + return member.GetValue(alias)?.ToString() ?? string.Empty; + } +} diff --git a/PersonalisationGroups/Providers/MemberType/IMemberTypeProvider.cs b/PersonalisationGroups/Providers/MemberType/IMemberTypeProvider.cs new file mode 100644 index 0000000..45ae45e --- /dev/null +++ b/PersonalisationGroups/Providers/MemberType/IMemberTypeProvider.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.MemberType; + +public interface IMemberTypeProvider +{ + string? GetMemberType(); +} diff --git a/PersonalisationGroups/Providers/MemberType/UmbracoMemberTypeProvider.cs b/PersonalisationGroups/Providers/MemberType/UmbracoMemberTypeProvider.cs new file mode 100644 index 0000000..96595ac --- /dev/null +++ b/PersonalisationGroups/Providers/MemberType/UmbracoMemberTypeProvider.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Http; +using Umbraco.Cms.Core.Services; + +namespace Our.Umbraco.PersonalisationGroups.Providers.MemberType; + +public class UmbracoMemberTypeProvider : IMemberTypeProvider +{ + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IMemberService _memberService; + + public UmbracoMemberTypeProvider(IHttpContextAccessor httpContextAccessor, IMemberService memberService) + { + _httpContextAccessor = httpContextAccessor; + _memberService = memberService; + } + + public string? GetMemberType() + { + return _httpContextAccessor.HttpContext?.User.Identity?.IsAuthenticated ?? false + ? GetAuthenticatedMemberType() + : string.Empty; + } + + private string? GetAuthenticatedMemberType() + { + var httpContext = _httpContextAccessor.HttpContext; + if (httpContext?.User?.Identity?.Name == null) + { + return null; + } + + var member = _memberService.GetByUsername(httpContext.User.Identity.Name); + if (member == null) + { + return string.Empty; + } + + return member.ContentType.Alias; + } +} diff --git a/PersonalisationGroups/Providers/NumberOfVisits/CookieNumberOfVisitsProvider.cs b/PersonalisationGroups/Providers/NumberOfVisits/CookieNumberOfVisitsProvider.cs new file mode 100644 index 0000000..919bfb6 --- /dev/null +++ b/PersonalisationGroups/Providers/NumberOfVisits/CookieNumberOfVisitsProvider.cs @@ -0,0 +1,29 @@ +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Providers.Cookie; + +namespace Our.Umbraco.PersonalisationGroups.Providers.NumberOfVisits; + +public class CookieNumberOfVisitsProvider : INumberOfVisitsProvider +{ + private readonly PersonalisationGroupsConfig _config; + private readonly ICookieProvider _cookieProvider; + + public CookieNumberOfVisitsProvider(IOptions config, ICookieProvider cookieProvider) + { + _config = config.Value; + _cookieProvider = cookieProvider; + } + + public int GetNumberOfVisits() + { + var cookieValue = _cookieProvider.GetCookieValue(_config.CookieKeyForTrackingNumberOfVisits); + + if (!string.IsNullOrEmpty(cookieValue) && int.TryParse(cookieValue, out int cookieNumericValue)) + { + return cookieNumericValue; + } + + return 0; + } +} diff --git a/PersonalisationGroups/Providers/NumberOfVisits/INumberOfVisitsProvider.cs b/PersonalisationGroups/Providers/NumberOfVisits/INumberOfVisitsProvider.cs new file mode 100644 index 0000000..1fc95b7 --- /dev/null +++ b/PersonalisationGroups/Providers/NumberOfVisits/INumberOfVisitsProvider.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.NumberOfVisits; + +public interface INumberOfVisitsProvider +{ + int GetNumberOfVisits(); +} diff --git a/PersonalisationGroups/Providers/PagesViewed/CookiePagesViewedProvider.cs b/PersonalisationGroups/Providers/PagesViewed/CookiePagesViewedProvider.cs new file mode 100644 index 0000000..5031090 --- /dev/null +++ b/PersonalisationGroups/Providers/PagesViewed/CookiePagesViewedProvider.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Providers.Cookie; + +namespace Our.Umbraco.PersonalisationGroups.Providers.PagesViewed; + +public class CookiePagesViewedProvider : IPagesViewedProvider +{ + private readonly PersonalisationGroupsConfig _config; + private readonly ICookieProvider _cookieProvider; + + public CookiePagesViewedProvider(IOptions config, ICookieProvider cookieProvider) + { + _config = config.Value; + _cookieProvider = cookieProvider; + } + + public IEnumerable GetNodeKeysViewed() + { + var cookieValue = _cookieProvider.GetCookieValue(_config.CookieKeyForTrackingPagesViewed); + + if (!string.IsNullOrEmpty(cookieValue)) + { + return cookieValue.ParsePageKeys(); + } + + return Enumerable.Empty(); + } +} diff --git a/PersonalisationGroups/Providers/PagesViewed/IPagesViewedProvider.cs b/PersonalisationGroups/Providers/PagesViewed/IPagesViewedProvider.cs new file mode 100644 index 0000000..d71ffa1 --- /dev/null +++ b/PersonalisationGroups/Providers/PagesViewed/IPagesViewedProvider.cs @@ -0,0 +1,9 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.PagesViewed; + +using System; +using System.Collections.Generic; + +public interface IPagesViewedProvider +{ + IEnumerable GetNodeKeysViewed(); +} diff --git a/PersonalisationGroups/Providers/PagesViewed/StringExtensions.cs b/PersonalisationGroups/Providers/PagesViewed/StringExtensions.cs new file mode 100644 index 0000000..a9a5b2a --- /dev/null +++ b/PersonalisationGroups/Providers/PagesViewed/StringExtensions.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Providers.PagesViewed; + +public static class StringExtensions +{ + public static List ParsePageKeys(this string commaSeparatedPageKeys) + { + return commaSeparatedPageKeys + .Split(',') + .Aggregate(new List(), + (result, value) => + { + if (Guid.TryParse(value, out var item)) + { + result.Add(item); + } + + return result; + }); + } +} diff --git a/PersonalisationGroups/Providers/Querystring/HttpContextQuerystringProvider.cs b/PersonalisationGroups/Providers/Querystring/HttpContextQuerystringProvider.cs new file mode 100644 index 0000000..33b1dc7 --- /dev/null +++ b/PersonalisationGroups/Providers/Querystring/HttpContextQuerystringProvider.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Http; + +namespace Our.Umbraco.PersonalisationGroups.Providers.Querystring; + +public class HttpContextQuerystringProvider : IQuerystringProvider +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpContextQuerystringProvider(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public IQueryCollection GetQuerystring() + { + return _httpContextAccessor.HttpContext?.Request.Query ?? new QueryCollection(); + } +} diff --git a/PersonalisationGroups/Providers/Querystring/IQuerystringProvider.cs b/PersonalisationGroups/Providers/Querystring/IQuerystringProvider.cs new file mode 100644 index 0000000..019aa01 --- /dev/null +++ b/PersonalisationGroups/Providers/Querystring/IQuerystringProvider.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Http; + +namespace Our.Umbraco.PersonalisationGroups.Providers.Querystring; + +public interface IQuerystringProvider +{ + IQueryCollection GetQuerystring(); +} diff --git a/PersonalisationGroups/Providers/Referrer/HttpContextReferrerProvider.cs b/PersonalisationGroups/Providers/Referrer/HttpContextReferrerProvider.cs new file mode 100644 index 0000000..c9f66ba --- /dev/null +++ b/PersonalisationGroups/Providers/Referrer/HttpContextReferrerProvider.cs @@ -0,0 +1,15 @@ +using Microsoft.AspNetCore.Http; + +namespace Our.Umbraco.PersonalisationGroups.Providers.Referrer; + +public class HttpContextReferrerProvider : IReferrerProvider +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpContextReferrerProvider(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public string? GetReferrer() => _httpContextAccessor.HttpContext?.Request.GetTypedHeaders().Referer?.AbsoluteUri; +} diff --git a/PersonalisationGroups/Providers/Referrer/IReferrerProvider.cs b/PersonalisationGroups/Providers/Referrer/IReferrerProvider.cs new file mode 100644 index 0000000..99c608e --- /dev/null +++ b/PersonalisationGroups/Providers/Referrer/IReferrerProvider.cs @@ -0,0 +1,6 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.Referrer; + +public interface IReferrerProvider +{ + string? GetReferrer(); +} diff --git a/PersonalisationGroups/Providers/RequestHeaders/HttpContextRequestHeadersProvider.cs b/PersonalisationGroups/Providers/RequestHeaders/HttpContextRequestHeadersProvider.cs new file mode 100644 index 0000000..48289a4 --- /dev/null +++ b/PersonalisationGroups/Providers/RequestHeaders/HttpContextRequestHeadersProvider.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Http; + +namespace Our.Umbraco.PersonalisationGroups.Providers.RequestHeaders; + +public class HttpContextRequestHeadersProvider : IRequestHeadersProvider +{ + private readonly IHttpContextAccessor _httpContextAccessor; + + public HttpContextRequestHeadersProvider(IHttpContextAccessor httpContextAccessor) + { + _httpContextAccessor = httpContextAccessor; + } + + public IHeaderDictionary GetHeaders() + { + return _httpContextAccessor.HttpContext?.Request.Headers ?? new HeaderDictionary(); + } +} diff --git a/PersonalisationGroups/Providers/RequestHeaders/IRequestHeadersProvider.cs b/PersonalisationGroups/Providers/RequestHeaders/IRequestHeadersProvider.cs new file mode 100644 index 0000000..ac1d6dc --- /dev/null +++ b/PersonalisationGroups/Providers/RequestHeaders/IRequestHeadersProvider.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Http; + +namespace Our.Umbraco.PersonalisationGroups.Providers.RequestHeaders; + +public interface IRequestHeadersProvider +{ + IHeaderDictionary GetHeaders(); +} diff --git a/PersonalisationGroups/Providers/Session/HttpContextSessionProvider.cs b/PersonalisationGroups/Providers/Session/HttpContextSessionProvider.cs new file mode 100644 index 0000000..22dcf72 --- /dev/null +++ b/PersonalisationGroups/Providers/Session/HttpContextSessionProvider.cs @@ -0,0 +1,17 @@ +using Umbraco.Cms.Core.Web; + +namespace Our.Umbraco.PersonalisationGroups.Providers.Session; + +public class HttpContextSessionProvider : ISessionProvider +{ + private readonly ISessionManager _sessionManager; + + public HttpContextSessionProvider(ISessionManager sessionManager) + { + _sessionManager = sessionManager; + } + + public bool KeyExists(string key) => _sessionManager.GetSessionValue(key) != null; + + public string? GetValue(string key) => _sessionManager.GetSessionValue(key)?.ToString() ?? null; +} diff --git a/PersonalisationGroups/Providers/Session/ISessionProvider.cs b/PersonalisationGroups/Providers/Session/ISessionProvider.cs new file mode 100644 index 0000000..cad374e --- /dev/null +++ b/PersonalisationGroups/Providers/Session/ISessionProvider.cs @@ -0,0 +1,8 @@ +namespace Our.Umbraco.PersonalisationGroups.Providers.Session; + +public interface ISessionProvider +{ + bool KeyExists(string key); + + string? GetValue(string key); +} diff --git a/PersonalisationGroups/Services/CriteriaService.cs b/PersonalisationGroups/Services/CriteriaService.cs new file mode 100644 index 0000000..8fb312c --- /dev/null +++ b/PersonalisationGroups/Services/CriteriaService.cs @@ -0,0 +1,81 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Criteria; +using Our.Umbraco.PersonalisationGroups.Extensions; +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Cms.Core.Cache; + +namespace Our.Umbraco.PersonalisationGroups.Services; + +public class CriteriaService : ICriteriaService +{ + private readonly PersonalisationGroupsConfig _config; + private readonly IServiceProvider _serviceProvider; + private readonly AppCaches _appCaches; + + public CriteriaService(IOptions config, IServiceProvider serviceProvider, AppCaches appCaches) + { + _config = config.Value; + _serviceProvider = serviceProvider; + _appCaches = appCaches; + } + + public IEnumerable GetAvailableCriteria() + { + var cacheKey = $"PersonalisationGroups_Criteria"; + var criteriaDictionary = _appCaches.RuntimeCache.Get(cacheKey, + () => BuildAvailableCriteria()) as Dictionary; + + var criteria = criteriaDictionary!.Values.Select(x => x); + + if (!string.IsNullOrEmpty(_config.IncludeCriteria)) + { + criteria = criteria + .Where(x => _config.IncludeCriteria.Split(',').Contains(x. Alias, StringComparer.InvariantCultureIgnoreCase)); + } + + if (!string.IsNullOrEmpty(_config.ExcludeCriteria)) + { + criteria = criteria + .Where(x => !_config.ExcludeCriteria.Split(',').Contains(x.Alias, StringComparer.InvariantCultureIgnoreCase)); + } + + return criteria.OrderBy(x => x.Name); + } + + /// + /// Helper to scan the loaded assemblies and retrieve the available personalisation group criteria (that implement the + /// interface + /// + private Dictionary BuildAvailableCriteria() + { + var lockObject = new object(); + lock (lockObject) + { + var criteria = new Dictionary(); + var type = typeof(IPersonalisationGroupCriteria); + var typesImplementingInterface = AppDomain.CurrentDomain.GetAssemblies() + .SelectMany(s => s.GetLoadableTypes()) + .Where(p => type.IsAssignableFrom(p) && p.IsClass) + .Select(x => ActivatorUtilities.CreateInstance(_serviceProvider, x) as IPersonalisationGroupCriteria) + .Where(x => x != null); + + foreach (var typeImplementingInterface in typesImplementingInterface) + { + // Aliases have to be unique - but in case they aren't, make sure we don't attempt + // to load a second criteria of the same alias. Issue #14. + if (criteria.ContainsKey(typeImplementingInterface!.Alias)) + { + continue; + } + + criteria.Add(typeImplementingInterface.Alias, typeImplementingInterface); + } + + return criteria; + } + } +} diff --git a/PersonalisationGroups/Services/GroupMatchingService.cs b/PersonalisationGroups/Services/GroupMatchingService.cs new file mode 100644 index 0000000..3c522c3 --- /dev/null +++ b/PersonalisationGroups/Services/GroupMatchingService.cs @@ -0,0 +1,238 @@ +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Criteria; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Extensions; + +namespace Our.Umbraco.PersonalisationGroups.Services; + +public class GroupMatchingService : IGroupMatchingService +{ + private readonly PersonalisationGroupsConfig _config; + private readonly ICriteriaService _criteriaService; + private readonly IStickyMatchService _stickyMatchService; + private readonly IPublishedValueFallback _publishedValueFallback; + + public GroupMatchingService( + IOptions config, + ICriteriaService criteriaService, + IStickyMatchService stickyMatchService, + IPublishedValueFallback publishedValueFallback) + { + _config = config.Value; + _criteriaService = criteriaService; + _stickyMatchService = stickyMatchService; + _publishedValueFallback = publishedValueFallback; + } + + /// + /// Gets the list of personalisation group content items associated with the current content item + /// + /// Instance of IPublished content + /// List of personalisation group content items + public IList GetPickedGroups(IPublishedElement content) + { + var propertyAlias = _config.GroupPickerAlias; + if (content.HasProperty(propertyAlias)) + { + var rawValue = content.Value(propertyAlias); + switch (rawValue) + { + case IEnumerable list: + return list.ToList(); + case IPublishedContent group: + return new List { group }; + } + } + + return new List(); + } + + public bool MatchGroup(IPublishedContent pickedGroup) => MatchGroups(new List { pickedGroup }); + + public bool MatchGroups(IList pickedGroups) + { + // Package is disabled, return default. + if (_config.DisablePackage) + { + return true; + } + + // Check each personalisation group assigned for a match with the current site visitor. + foreach (var group in pickedGroups) + { + var definition = group.Value(_publishedValueFallback, AppConstants.PersonalisationGroupDefinitionPropertyAlias) + ?? throw new InvalidOperationException($"Could not retrieve typed definition from: {group.Name}"); + if (_stickyMatchService.IsStickyMatch(definition, group.Id)) + { + return true; + } + + var matchCount = CountMatchingDefinitionDetails(definition); + + // If matching any and matched at least one, or matching all and matched all - we've matched one of the definitions + // associated with a selected personalisation group. + if (definition.Match == PersonalisationGroupDefinitionMatch.Any && matchCount > 0 || + definition.Match == PersonalisationGroupDefinitionMatch.All && matchCount == definition.Details.Count()) + { + _stickyMatchService.MakeStickyMatch(definition, group.Id); + return true; + } + } + + // If we've got here, we haven't found a match. + return false; + } + + /// + /// Gets a count of the number of the definition details for a given personalisation group definition that matches + /// the current site visitor. + /// + /// Personalisation group definition + /// Number of definition details that match + public int CountMatchingDefinitionDetails(PersonalisationGroupDefinition definition) + { + var matchCount = 0; + foreach (var detail in definition.Details) + { + var isMatch = IsMatch(detail); + if (isMatch) + { + matchCount++; + } + + // We can short-cut here if matching any and found one match, or matching all and found one mismatch + if ((isMatch && definition.Match == PersonalisationGroupDefinitionMatch.Any) || + (!isMatch && definition.Match == PersonalisationGroupDefinitionMatch.All)) + { + break; + } + } + + return matchCount; + } + + /// + /// Checks if a given detail record of a personalisation group definition matches the current site visitor. + /// + /// Personalisation group definition detail record + /// True of the current site visitor matches the definition + private bool IsMatch(PersonalisationGroupDefinitionDetail definitionDetail) + { + var criterium = _criteriaService.GetAvailableCriteria().SingleOrDefault(x => string.Equals(x.Alias, definitionDetail.Alias, StringComparison.InvariantCultureIgnoreCase)); + if (criterium == null) + { + throw new KeyNotFoundException($"Personalisation group criteria not found with alias '{definitionDetail.Alias}'"); + } + + return criterium.MatchesVisitor(definitionDetail.Definition); + } + + public bool MatchGroupsByName(string[] groupNames, IList groups, PersonalisationGroupDefinitionMatch matchType) + { + // Package is disabled, return default + if (_config.DisablePackage) + { + return true; + } + + var matches = 0; + foreach (var groupName in groupNames) + { + var group = groups + .FirstOrDefault(x => string.Equals(x.Name, groupName, StringComparison.InvariantCultureIgnoreCase)); + if (@group == null) + { + continue; + } + + if (MatchGroup(@group)) + { + if (matchType == PersonalisationGroupDefinitionMatch.Any) + { + return true; + } + + matches++; + } + else + { + if (matchType == PersonalisationGroupDefinitionMatch.All) + { + return false; + } + } + } + + return matches == groupNames.Length; + } + + public int ScoreGroup(IPublishedContent pickedGroup) => ScoreGroups(new List { pickedGroup }); + + public int ScoreGroups(IList pickedGroups) + { + // Package is disabled, return default + if (_config.DisablePackage) + { + return 0; + } + + // Check each personalisation group assigned for a match with the current site visitor + var score = 0; + foreach (var group in pickedGroups) + { + var definition = group.Value(_publishedValueFallback, AppConstants.PersonalisationGroupDefinitionPropertyAlias) + ?? throw new InvalidOperationException($"Could not retrieve typed definition from: {group.Name}"); + if (_stickyMatchService.IsStickyMatch(definition, group.Id)) + { + score += definition.Score; + continue; + } + + var matchCount = CountMatchingDefinitionDetails(definition); + + // If matching any and matched at least one, or matching all and matched all - we've matched one of the definitions + // associated with a selected personalisation group + if ((definition.Match == PersonalisationGroupDefinitionMatch.Any && matchCount > 0) || + (definition.Match == PersonalisationGroupDefinitionMatch.All && matchCount == definition.Details.Count())) + { + _stickyMatchService.MakeStickyMatch(definition, group.Id); + score += definition.Score; + } + } + + return score; + } + + public string CreatePersonalisationGroupsHashForVisitor(IPublishedContent personalisationGroupsRootNode) + { + var groups = personalisationGroupsRootNode.Descendants(AppConstants.DocumentTypeAliases.PersonalisationGroup); + var sb = new StringBuilder(); + foreach (var group in groups) + { + var definition = group.Value(AppConstants.PersonalisationGroupDefinitionPropertyAlias) + ?? throw new InvalidOperationException($"Could not retrieve typed definition from: {group.Name}"); + AppendMatchedGroupDetailToVisitorHashString(sb, definition, group.CreatorName() ?? string.Empty); + } + + return sb.ToString().GetHashCode().ToString(); + } + + private void AppendMatchedGroupDetailToVisitorHashString(StringBuilder sb, PersonalisationGroupDefinition definition, string name) + { + var matchCount = CountMatchingDefinitionDetails(definition); + var matched = (definition.Match == PersonalisationGroupDefinitionMatch.Any && matchCount > 0) || + (definition.Match == PersonalisationGroupDefinitionMatch.All && matchCount == definition.Details.Count()); + + if (sb.Length > 0) + { + sb.Append(','); + } + + sb.AppendFormat("{0}={1}", name, matched); + } +} diff --git a/PersonalisationGroups/Services/ICriteriaService.cs b/PersonalisationGroups/Services/ICriteriaService.cs new file mode 100644 index 0000000..39563fd --- /dev/null +++ b/PersonalisationGroups/Services/ICriteriaService.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; +using Our.Umbraco.PersonalisationGroups.Criteria; + +namespace Our.Umbraco.PersonalisationGroups.Services; + +public interface ICriteriaService +{ + IEnumerable GetAvailableCriteria(); +} diff --git a/PersonalisationGroups/Services/IGroupMatchingService.cs b/PersonalisationGroups/Services/IGroupMatchingService.cs new file mode 100644 index 0000000..d54b715 --- /dev/null +++ b/PersonalisationGroups/Services/IGroupMatchingService.cs @@ -0,0 +1,24 @@ +using Our.Umbraco.PersonalisationGroups.Criteria; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models.PublishedContent; + +namespace Our.Umbraco.PersonalisationGroups.Services; + +public interface IGroupMatchingService +{ + IList GetPickedGroups(IPublishedElement content); + + bool MatchGroup(IPublishedContent pickedGroup); + + bool MatchGroups(IList pickedGroups); + + int CountMatchingDefinitionDetails(PersonalisationGroupDefinition definition); + + bool MatchGroupsByName(string[] groupNames, IList groups, PersonalisationGroupDefinitionMatch matchType); + + int ScoreGroup(IPublishedContent pickedGroup); + + int ScoreGroups(IList pickedGroups); + + string CreatePersonalisationGroupsHashForVisitor(IPublishedContent personalisationGroupsRootNode); +} diff --git a/PersonalisationGroups/Services/IStickyMatchService.cs b/PersonalisationGroups/Services/IStickyMatchService.cs new file mode 100644 index 0000000..4adc34e --- /dev/null +++ b/PersonalisationGroups/Services/IStickyMatchService.cs @@ -0,0 +1,10 @@ +using Our.Umbraco.PersonalisationGroups.Criteria; + +namespace Our.Umbraco.PersonalisationGroups.Services; + +public interface IStickyMatchService +{ + bool IsStickyMatch(PersonalisationGroupDefinition definition, int groupNodeId); + + void MakeStickyMatch(PersonalisationGroupDefinition definition, int groupNodeId); +} diff --git a/PersonalisationGroups/Services/IUserActivityTracker.cs b/PersonalisationGroups/Services/IUserActivityTracker.cs new file mode 100644 index 0000000..515a05d --- /dev/null +++ b/PersonalisationGroups/Services/IUserActivityTracker.cs @@ -0,0 +1,10 @@ +using System; + +namespace Our.Umbraco.PersonalisationGroups.Services; + +public interface IUserActivityTracker +{ + void TrackPageView(Guid pageKey); + + void TrackSession(); +} \ No newline at end of file diff --git a/PersonalisationGroups/Services/StickyMatchService.cs b/PersonalisationGroups/Services/StickyMatchService.cs new file mode 100644 index 0000000..547c17f --- /dev/null +++ b/PersonalisationGroups/Services/StickyMatchService.cs @@ -0,0 +1,116 @@ +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Criteria; +using Our.Umbraco.PersonalisationGroups.Providers.Cookie; +using Our.Umbraco.PersonalisationGroups.Providers.DateTime; +using System; +using System.Linq; + +namespace Our.Umbraco.PersonalisationGroups.Services; + +public class StickyMatchService : IStickyMatchService +{ + private readonly PersonalisationGroupsConfig _config; + private readonly ICookieProvider _cookieProvider; + private readonly IDateTimeProvider _dateTimeProvider; + + public StickyMatchService(IOptions config, ICookieProvider cookieProvider, IDateTimeProvider dateTimeProvider) + { + _config = config.Value; + _cookieProvider = cookieProvider; + _dateTimeProvider = dateTimeProvider; + } + + public bool IsStickyMatch(PersonalisationGroupDefinition definition, int groupNodeId) + { + if (definition.Duration == PersonalisationGroupDefinitionDuration.Page) + { + return false; + } + + var key = GetCookieKeyForMatchedGroups(definition.Duration); + var cookieValue = _cookieProvider.GetCookieValue(key); + return !string.IsNullOrEmpty(cookieValue) && IsGroupMatched(cookieValue, groupNodeId); + } + + /// + /// Makes a matched group sticky for the visitor via a cookie setting according to group definition + /// + /// Matched group definition + /// Id of the matched groups node + public void MakeStickyMatch(PersonalisationGroupDefinition definition, int groupNodeId) + { + if (definition.Duration == PersonalisationGroupDefinitionDuration.Page) + { + return; + } + + var key = GetCookieKeyForMatchedGroups(definition.Duration); + var value = _cookieProvider.GetCookieValue(key); + if (!string.IsNullOrEmpty(value)) + { + value = AppendGroupNodeId(value, groupNodeId); + } + else + { + value = groupNodeId.ToString(); + } + + DateTime? expires = null; + if (definition.Duration == PersonalisationGroupDefinitionDuration.Visitor) + { + var cookieExpiryInDays = _config.PersistentMatchedGroupsCookieExpiryInDays; + expires = _dateTimeProvider.GetCurrentDateTime().AddDays(cookieExpiryInDays); + } + + _cookieProvider.SetCookie(key, value, expires); + } + + /// + /// Retrieves the cookie key to use for the matched groups + /// + /// Match group duration + /// Cookie key to use + private string GetCookieKeyForMatchedGroups(PersonalisationGroupDefinitionDuration duration) + { + switch (duration) + { + case PersonalisationGroupDefinitionDuration.Session: + return _config.CookieKeyForSessionMatchedGroups; + case PersonalisationGroupDefinitionDuration.Visitor: + return _config.CookieKeyForPersistentMatchedGroups; + default: + throw new InvalidOperationException("Only session and visitor personalisation groups can be tracked."); + } + } + + /// + /// Adds a matched group to the cookie for sticky groups + /// + /// Existing cookie value of matched group node Ids + /// Id of the matched groups node + /// Updated cookie value + private static string AppendGroupNodeId(string matchedGroupIds, int groupNodeId) + { + // Shouldn't exist as we don't try to append an already sticky group match, but just to be sure. + if (!IsGroupMatched(matchedGroupIds, groupNodeId)) + { + matchedGroupIds = matchedGroupIds + "," + groupNodeId; + } + + return matchedGroupIds; + } + + /// + /// Checks if group is matched in tracking cookie value + /// + /// Existing cookie value of matched group node Ids + /// Id of the matched groups node + /// True if matched + private static bool IsGroupMatched(string matchedGroupIds, int groupNodeId) + { + return matchedGroupIds + .Split(',') + .Any(x => int.Parse(x) == groupNodeId); + } +} diff --git a/PersonalisationGroups/Services/UserActivityTracker.cs b/PersonalisationGroups/Services/UserActivityTracker.cs new file mode 100644 index 0000000..3af5b51 --- /dev/null +++ b/PersonalisationGroups/Services/UserActivityTracker.cs @@ -0,0 +1,79 @@ +using Microsoft.Extensions.Options; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Providers.Cookie; +using Our.Umbraco.PersonalisationGroups.Providers.DateTime; +using Our.Umbraco.PersonalisationGroups.Providers.PagesViewed; +using System; + +namespace Our.Umbraco.PersonalisationGroups.Services; + +public class UserActivityTracker : IUserActivityTracker +{ + private readonly PersonalisationGroupsConfig _config; + private readonly ICookieProvider _cookieProvider; + private readonly IDateTimeProvider _dateTimeProvider; + + public UserActivityTracker(IOptions config, ICookieProvider cookieProvider, IDateTimeProvider dateTimeProvider) + { + _config = config.Value; + _cookieProvider = cookieProvider; + _dateTimeProvider = dateTimeProvider; + } + + public void TrackPageView(Guid pageKey) + { + var key = _config.CookieKeyForTrackingPagesViewed; + var value = _cookieProvider.GetCookieValue(key); + if (!string.IsNullOrEmpty(value)) + { + value = AppendPageIdIfNotPreviouslyViewed(value, pageKey); + } + else + { + value = pageKey.ToString(); + } + + var expires = _dateTimeProvider.GetCurrentDateTime().AddDays(_config.ViewedPagesTrackingCookieExpiryInDays); + _cookieProvider.SetCookie(key, value, expires); + } + + internal static string AppendPageIdIfNotPreviouslyViewed(string viewedPageIds, Guid pageKey) + { + var keys = viewedPageIds.ParsePageKeys(); + + if (!keys.Contains(pageKey)) + { + keys.Add(pageKey); + } + + return string.Join(",", keys); + } + + public void TrackSession() + { + var sessionCookieKey = _config.CookieKeyForTrackingIfSessionAlreadyTracked; + + // Check if session cookie present. + var sessionCookieExists = _cookieProvider.CookieExists(sessionCookieKey); + if (!sessionCookieExists) + { + // If not, create or update the number of visits cookie. + var numberOfVisitsCookeKey = _config.CookieKeyForTrackingNumberOfVisits; + var value = _cookieProvider.GetCookieValue(numberOfVisitsCookeKey); + if (!string.IsNullOrEmpty(value)) + { + value = int.TryParse(value, out int cookieValue) ? (cookieValue + 1).ToString() : "1"; + } + else + { + value = "1"; + } + + var expires = _dateTimeProvider.GetCurrentDateTime().AddDays(_config.NumberOfVisitsTrackingCookieExpiryInDays); + _cookieProvider.SetCookie(numberOfVisitsCookeKey, value, expires); + + // Set the session cookie so we don't keep updating on each request + _cookieProvider.SetCookie(_config.CookieKeyForTrackingIfSessionAlreadyTracked, "1"); + } + } +} \ No newline at end of file diff --git a/PersonalisationGroups/ServicesConfiguration.cs b/PersonalisationGroups/ServicesConfiguration.cs new file mode 100644 index 0000000..2d60c81 --- /dev/null +++ b/PersonalisationGroups/ServicesConfiguration.cs @@ -0,0 +1,125 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.OpenApi.Models; +using Our.Umbraco.PersonalisationGroups.Api.Configuration; +using Our.Umbraco.PersonalisationGroups.Configuration; +using Our.Umbraco.PersonalisationGroups.Middleware; +using Our.Umbraco.PersonalisationGroups.Providers.AuthenticationStatus; +using Our.Umbraco.PersonalisationGroups.Providers.Cookie; +using Our.Umbraco.PersonalisationGroups.Providers.DateTime; +using Our.Umbraco.PersonalisationGroups.Providers.GeoLocation; +using Our.Umbraco.PersonalisationGroups.Providers.Host; +using Our.Umbraco.PersonalisationGroups.Providers.Ip; +using Our.Umbraco.PersonalisationGroups.Providers.MemberGroup; +using Our.Umbraco.PersonalisationGroups.Providers.MemberProfileField; +using Our.Umbraco.PersonalisationGroups.Providers.MemberType; +using Our.Umbraco.PersonalisationGroups.Providers.NumberOfVisits; +using Our.Umbraco.PersonalisationGroups.Providers.PagesViewed; +using Our.Umbraco.PersonalisationGroups.Providers.Querystring; +using Our.Umbraco.PersonalisationGroups.Providers.Referrer; +using Our.Umbraco.PersonalisationGroups.Providers.RequestHeaders; +using Our.Umbraco.PersonalisationGroups.Providers.Session; +using Our.Umbraco.PersonalisationGroups.Services; +using Swashbuckle.AspNetCore.SwaggerGen; +using System; +using Umbraco.Cms.Core.DependencyInjection; +using Umbraco.Cms.Web.Common.ApplicationBuilder; +using Umbraco.Extensions; + +namespace Our.Umbraco.PersonalisationGroups; + +public static class ServicesConfiguration +{ + public static IUmbracoBuilder AddPersonalisationGroups(this IUmbracoBuilder builder, IConfiguration config) + { + var configSection = config.GetSection("Umbraco:PersonalisationGroups"); + AddConfiguration(builder.Services, configSection); + + AddServices(builder.Services); + + AddProviders(builder.Services, configSection); + + AddMiddleware(builder.Services, configSection); + + AddSwaggerGenOptions(builder.Services); + + return builder; + } + + private static void AddConfiguration(IServiceCollection services, IConfigurationSection configSection) + { + services.Configure(configSection); + } + + private static void AddServices(IServiceCollection services) + { + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + } + + private static void AddProviders(IServiceCollection services, IConfigurationSection configSection) + { + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + switch (configSection.GetValue("CountryCodeProvider")) + { + case CountryCodeProvider.MaxMindDatabase: + services.AddUnique(); + break; + case CountryCodeProvider.CdnHeader: + services.AddUnique(); + break; + } + + services.AddUnique(); + services.AddUnique(); + services.AddSingleton(); + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + services.AddUnique(); + } + + private static void AddMiddleware(IServiceCollection services, IConfigurationSection configSection) + { + services.AddSingleton(); + + var disableUserActivityTracking = configSection.GetValue("DisableUserActivityTracking"); + if (!disableUserActivityTracking) + { + services.Configure(options => + options.AddFilter(new UmbracoPipelineFilter(nameof(TrackUserActivityMiddleware)) + { + PostPipeline = app => app.UseMiddleware() + })); + } + } + + public static void AddSwaggerGenOptions(IServiceCollection services) + { + services.Configure(options => + { + options.SwaggerDoc( + AppConstants.ManagementApi.ApiName, + new OpenApiInfo + { + Title = AppConstants.ManagementApi.ApiTitle, + Version = "Latest", + Description = $"Describes the {AppConstants.ManagementApi.ApiTitle} available for configuring personalisation groups within the Umbraco backoffice." + }); + + options.OperationFilter(); + options.CustomOperationIds(e => $"{e.ActionDescriptor.RouteValues["action"]}"); + }); + } +} diff --git a/PersonalisationGroups/UmbracoPersonalisationGroups.csproj b/PersonalisationGroups/UmbracoPersonalisationGroups.csproj index 168ffa5..622d237 100644 --- a/PersonalisationGroups/UmbracoPersonalisationGroups.csproj +++ b/PersonalisationGroups/UmbracoPersonalisationGroups.csproj @@ -1,4 +1,4 @@ - + Our.Umbraco.PersonalisationGroups @@ -9,50 +9,53 @@ Umbraco Umbraco-Marketplace icon.png NuGetReadMe.md + /App_Plugins/PersonalisationGroups + false + enable + nullable - - true - Always - - - True - build - + + - - - + + + - + - - true - Always - + + + + - - true - Always - + + - - + + <_Parameter1>Our.Umbraco.PersonalisationGroups.Tests + - - - + + + + + true + + + + diff --git a/PersonalisationGroups/wwwroot/umbraco-package.json b/PersonalisationGroups/wwwroot/umbraco-package.json new file mode 100644 index 0000000..1e5a2e8 --- /dev/null +++ b/PersonalisationGroups/wwwroot/umbraco-package.json @@ -0,0 +1,13 @@ +{ + "id": "UmbracoPersonalisationGroups", + "name": "Personalisation Groups", + "version": "4.0.0-rc1", + "extensions": [ + { + "name": "Umbraco EntryPoint", + "alias": "PersonalisationGroups.EntryPoint", + "type": "entryPoint", + "js": "/App_Plugins/PersonalisationGroups/personalisation-groups.js" + } + ] +} diff --git a/ReadMe.md b/ReadMe.md index 249f4cd..33af094 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -40,7 +40,7 @@ It contains a few different pieces: Installation is via NuGet: ``` - PM> Install-Package UmbracoPersonalisationGroups +PM> Install-Package UmbracoPersonalisationGroups ``` Once installed, the default Umbraco `StartUp.cs` class should be augmented with additional extension methods for registering the package: `AddPersonalisationGroups` and `UsePersonalisationGroupsEndpoints`. When complete, it should look like this: @@ -77,7 +77,30 @@ Once installed, the default Umbraco `StartUp.cs` class should be augmented with } ``` -The package includes a migration that will create the necesssary document types, data types and root content nodes. +> [!NOTE] +> For Umbraco 14, the addition of `u.UsePersonalisationGroupsEndpoints();` is not required. + +The package includes a migration that will create the necessary document types, data types and root content nodes. + +### Upgrades + +To upgrade update the reference to the version of the NuGet package in your `.csproj` file and rebuild. + +#### Version specific upgrade notes + +Upgrading an Umbraco database that used the package with Umbraco 13 or below to Umbraco 14 will carry over your groups and definitions. Please note though breaking changes listed at the bottom of this document for version 4 of the package. + +Once the database is upgraded you will need to carry out these two one-off actions: + +- Navigate to _Settings > Data Types_ and find "Personalisation Group Definition" +- Note that the property editor will be defined as "personalisationGroupDefinition | Umbraco.Label" +- Click "Change" and select the editor "Personalisation Group Definition Editor" +- Save the data type + +Not essential, but you will likely also find the "Personalisation Groups Folder" content node is missing an icon. To fix: + +- Navigate to _Settings > Document Types_ and find "Personalisation Groups Folder" +- Select an icon (`icon-folder` in green is the standard provided with a clean install) ### Example usage @@ -114,7 +137,7 @@ A typical example would be to personalise a list of repeated content to only sho ``` @using Umbraco.Cms.Core.Models.PublishedContent; @using Umbraco.Extensions; - @using Our.Umbraco.PersonalisationGroups.Core.Services; + @using Our.Umbraco.PersonalisationGroups.Services; @inject IGroupMatchingService GroupMatchingService; @@ -197,7 +220,7 @@ The keys for this cookie and session can be amended in configuration if required No configuration is required if you are happy to accept the default behaviour of the package. -Optioanl keys and values can be added to your `appSettings.json` if required to amend this, within _Umbraco:PersonalisationGroups_, e.g.: +Optional keys and values can be added to your `appSettings.json` if required to amend this, within _Umbraco:PersonalisationGroups_, e.g.: ``` "Umbraco": { @@ -214,8 +237,8 @@ The following configuration is available: - `GeoLocationCountryDatabasePath` - amends the convention path for where the IP-country geolocation database can be found. See the "Country and region" section below for more details. - `GeoLocationCityDatabasePath` - amends the convention path for where the IP-city geolocation database can be found. See the "Country and region" section below for more details. - `GeoLocationRegionListPath` - if provided, the file referenced will be used to construct the list of regions for selection. See the "Country and region" section below for more details. -- `IncludeCriteria` - provides the specific list of criteria to make available for creating personsaliation groups. -- `ExcludeCriteria` - provides a list of criteria to exclude from the full list of available criteria made available for creating personsaliation groups. +- `IncludeCriteria` - provides the specific list of criteria to make available for creating personalisation groups. +- `ExcludeCriteria` - provides a list of criteria to exclude from the full list of available criteria made available for creating personalisation groups. - `NumberOfVisitsTrackingCookieExpiryInDays` - sets the expiry time for the cookie used for number of visits page tracking for the pages viewed criteria (default if not provided is 90). - `ViewedPagesTrackingCookieExpiryInDays` - sets the expiry time for the cookie used for viewed page tracking for the pages viewed criteria (default if not provided is 90). - `CookieKeyForTrackingNumberOfVisits` - defines the cookie key name used for tracking the number of visits. @@ -260,7 +283,9 @@ It then makes these criteria available to application logic that needs to create `PersonalisationGroupDefinitionPropertyEditor` defines an Umbraco property editor for the definition of the personalisation groups. It has a related angular view and controller, and also ensures the angular assets required for the specific criteria that are provided with the core package are loaded and available for use. -### Angular views and controllers +### Front-end components + +#### Umbraco 9 to 13: AngularJs views and controllers The primary view and controller for the property editor are `editor.html` and `editor.controller.js` respectively. @@ -268,6 +293,14 @@ In addition to these, each criteria has it's own view and controller that provid Each criteria also has an angular service named `definition.translator.js` responsible for translating the JSON syntax into something more human readable. So again for example the `DayOfWeekPersonalisationGroupCriteria` will render "Sunday, Tuesday, Thursday" from `[1, 3, 5]`. +#### Umbraco 14+: Web components + +With the version created for Umbraco 14 the angularjs views and controllers have been moved into web components. + +The editors for each criteria are implemented as Umbraco property editors. + +The translators a provided as implementations of a custom manifest type introduced by the package, `ManifestPersonalisationGroupDefinitionDetailTranslator`. + ### PublishedContentExtensions / PublishedElementExtensions `PublishedElementExtensions` defines the extension methods on `IPublishedElement` (and it's derived interface `IPublishedContent`) named `ShowToVisitor(IGroupMatchingService groupMatchingService, bool showIfNoGroupsDefined = true)` and `ScoreForVisitor(IGroupMatchingService groupMatchingService, bool showIfNoGroupsDefined = true)`. This implements the following logic: @@ -304,7 +337,9 @@ If you don't want this cookie to be written, you can remove this criteria from t ## How to extend it -The idea moving forward is that not every criteria will necessarily be provided by the core package - it should be extensible by developers looking to implement something that might be quite specific to their application. This should be mostly straightforward. Due to the fact that the criteria that are made available come from a scan of all loaded assemblies, it should only be necessary to provide a dll with an implementation of `IPersonalisationGroupCriteria` and a unique `Alias` property, along with the definition editor angular view, controller and translation service - `definition.editor.html`, `definition.editor.controller.js` and `definition.definition.translator.js` respectively. +The idea moving forward is that not every criteria will necessarily be provided by the core package - it should be extensible by developers looking to implement something that might be quite specific to their application. Due to the fact that the criteria that are made available come from a scan of all loaded assemblies, it should only be necessary to provide a dll with an implementation of `IPersonalisationGroupCriteria` and a unique `Alias` property, along with the appropriate front-end components. + +For versions up to and include Umbraco 13, these are the definition editor angular view, controller and translation service - `definition.editor.html`, `definition.editor.controller.js` and `definition.definition.translator.js` respectively. As with other Umbraco packages, you'll also need to create a `package.manifest` file listing out the additional JavaScript files you need, e.g.: @@ -317,14 +352,20 @@ As with other Umbraco packages, you'll also need to create a `package.manifest` } ``` -As well as the interface, there's a helper base class `PersonalisationGroupCriteriaBase` that you can inherit from that provides some useful methods for matching values and regular expressions. This isn't required though for the criteria to be recognised and used. +For Umbraco 14 and above you will need to register two components using the manifest system. One, a property editor, for the interface for editing your criteria. And second, an implementation of the TypeScript interface `PersonalisationGroupDefinitionDetailTranslatorApi`, registered via the custom manifest type `ManifestPersonalisationGroupDefinitionDetailTranslator. -The C# files can sit anywhere of course. +As well as the `IPersonalisationGroupCriteria` interface, there's a helper base class `PersonalisationGroupCriteriaBase` that you can inherit from that provides some useful methods for matching values and regular expressions. This isn't required though for the criteria to be recognised and used. -Prior to 3.1, the client-side files had to live in `App_Plugins/PersonalisationGroups/Criteria/`, and the manifest file in `App_Plugins/PersonalisationGroups/`. This [caused issues](https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/issues/2) with custom criteria though, as they would be removed on each build. +Prior to 3.1, when running Umbraco 13 or lower, the client-side files had to live in `App_Plugins/PersonalisationGroups/Criteria/`, and the manifest file in `App_Plugins/PersonalisationGroups/`. This [caused issues](https://github.com/AndyButland/UmbracoPersonalisationGroupsCore/issues/2) with custom criteria though, as they would be removed on each build. To resolve that, `IPersonalisationGroupCriteria` has a property called `ClientAssetsFolder` that can be set to provide a custom location for the files. For example, rather than `PersonalisationGroups/Criteria`, you can set it to `MyCustomFolder/Criteria`, and store your client-side asset files in `App_Plugins/MyCustomFolder/Criteria`. +The source code is the best place to look for details of the specific syntax and implementation you will need to provide for your custom criteria. + +Examples for Umbraco 9 to 13 are found in the `support/3` branch. + +And for Umbraco 14 in `develop`. + ## Working with caching Caching - at least at the page level - and personalisation don't really play nicely together. Such caching will normally be varied by the URL but with personalisation we are displaying different content to different users, so we don't want the cached version of a page customised to particular user being displayed to the next. @@ -363,6 +404,33 @@ To do this you can exclude such critieria by alias via configuration, the two pr If you needed to personalise by these criteria - number of pages viewed and/or number of visits - it would be necessary to implement an alternate criteria that uses a different storage mechanism (such as a custom table or hooked into an analytics engine). +## Management API (for Umbraco 14+) + +Following patterns established for Umbraco CMS itself and other packages, the data needed in backoffice is provided by a management API. + +This can be found at `/umbraco/swagger/index.html?urls.primaryName=Personalisation+Groups+Management+API` + +## Package development (for Umbraco 14+) + +To build and run (assuming a test site in the solution root): + +``` +cd TestWebapp.V14\ +dotnet run +``` + +``` +cd PersonalisationGroups\Client +npm i +npm run build +``` + +If management API changes are made, in order to generate updated typed front-end services and types: + +``` +npm run generate:api +``` + ## Version history See [here](https://github.com/AndyButland/UmbracoPersonalisationGroups#version-history) for history of the package supporting Umbraco V7 and V8. @@ -404,4 +472,11 @@ See [here](https://github.com/AndyButland/UmbracoPersonalisationGroups#version-h - 3.3.0 - Surfaced the method `CountMatchingDefinitionDetails`, available in an earlier version of the package, via the `IGroupMatchingService` interface. - 3.4.0 - - Used the secure option for all cookies. \ No newline at end of file + - Used the secure option for all cookies. +- 4.0.0-rc1 + - Package updated to work with Umbraco 14. + +> [!WARNING] +> The 4.0.0. release running on Umbraco 14 contains various breaking changes at the code levelrelated to the backoffice update, the move to a single project using an RCL, and the migration of controllers into a management API. +> From a data perspective please note: +> - The "pages viewed" criteria now uses GUID keys rather than integer IDs. \ No newline at end of file diff --git a/UmbracoPersonalisationGroupsCore.sln b/UmbracoPersonalisationGroupsCore.sln index e1503d8..689b042 100644 --- a/UmbracoPersonalisationGroupsCore.sln +++ b/UmbracoPersonalisationGroupsCore.sln @@ -7,24 +7,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UmbracoPersonalisationGroup EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UmbracoPersonalisationGroups.Tests", "PersonalisationGroups.Tests\UmbracoPersonalisationGroups.Tests.csproj", "{4CDFDC2F-332E-4FF0-868F-AFA214563288}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UmbracoPersonalisationGroups.Core", "PersonalisationGroups.Core\UmbracoPersonalisationGroups.Core.csproj", "{27C6FC82-F414-467D-B88F-FBB414842348}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A433AA70-D284-4033-9694-4396E817D892}" ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore Directory.Build.props = Directory.Build.props ReadMe.md = ReadMe.md umbraco-marketplace.json = umbraco-marketplace.json EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWebApp.V9", "TestWebApp.V9\TestWebApp.V9.csproj", "{11FB64F5-6597-4FED-A1C5-93439896E1CA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWebApp.V10", "TestWebApp.V10\TestWebApp.V10.csproj", "{ACD5082C-DC54-4F83-9EF9-2569C061DC34}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWebApp.V11", "TestWebApp.V11\TestWebApp.V11.csproj", "{B79D85E7-1CF6-4B65-A429-C987D7075ED8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWebApp.V12", "TestWebApp.V12\TestWebApp.V12.csproj", "{EA0A03D8-AB1D-4100-B38D-B01B921231A4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWebApp.V13", "TestWebApp.V13\TestWebApp.V13.csproj", "{8C5B30BE-6BEA-4F30-AE27-8BE6CCEB159C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestWebApp.V14", "TestWebApp.V14\TestWebApp.V14.csproj", "{C67B5AD8-D4FA-4730-B44C-787C8CAF65ED}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -40,30 +31,10 @@ Global {4CDFDC2F-332E-4FF0-868F-AFA214563288}.Debug|Any CPU.Build.0 = Debug|Any CPU {4CDFDC2F-332E-4FF0-868F-AFA214563288}.Release|Any CPU.ActiveCfg = Release|Any CPU {4CDFDC2F-332E-4FF0-868F-AFA214563288}.Release|Any CPU.Build.0 = Release|Any CPU - {27C6FC82-F414-467D-B88F-FBB414842348}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {27C6FC82-F414-467D-B88F-FBB414842348}.Debug|Any CPU.Build.0 = Debug|Any CPU - {27C6FC82-F414-467D-B88F-FBB414842348}.Release|Any CPU.ActiveCfg = Release|Any CPU - {27C6FC82-F414-467D-B88F-FBB414842348}.Release|Any CPU.Build.0 = Release|Any CPU - {11FB64F5-6597-4FED-A1C5-93439896E1CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {11FB64F5-6597-4FED-A1C5-93439896E1CA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {11FB64F5-6597-4FED-A1C5-93439896E1CA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {11FB64F5-6597-4FED-A1C5-93439896E1CA}.Release|Any CPU.Build.0 = Release|Any CPU - {ACD5082C-DC54-4F83-9EF9-2569C061DC34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ACD5082C-DC54-4F83-9EF9-2569C061DC34}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ACD5082C-DC54-4F83-9EF9-2569C061DC34}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ACD5082C-DC54-4F83-9EF9-2569C061DC34}.Release|Any CPU.Build.0 = Release|Any CPU - {B79D85E7-1CF6-4B65-A429-C987D7075ED8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B79D85E7-1CF6-4B65-A429-C987D7075ED8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B79D85E7-1CF6-4B65-A429-C987D7075ED8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B79D85E7-1CF6-4B65-A429-C987D7075ED8}.Release|Any CPU.Build.0 = Release|Any CPU - {EA0A03D8-AB1D-4100-B38D-B01B921231A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA0A03D8-AB1D-4100-B38D-B01B921231A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA0A03D8-AB1D-4100-B38D-B01B921231A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA0A03D8-AB1D-4100-B38D-B01B921231A4}.Release|Any CPU.Build.0 = Release|Any CPU - {8C5B30BE-6BEA-4F30-AE27-8BE6CCEB159C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8C5B30BE-6BEA-4F30-AE27-8BE6CCEB159C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8C5B30BE-6BEA-4F30-AE27-8BE6CCEB159C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8C5B30BE-6BEA-4F30-AE27-8BE6CCEB159C}.Release|Any CPU.Build.0 = Release|Any CPU + {C67B5AD8-D4FA-4730-B44C-787C8CAF65ED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C67B5AD8-D4FA-4730-B44C-787C8CAF65ED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C67B5AD8-D4FA-4730-B44C-787C8CAF65ED}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C67B5AD8-D4FA-4730-B44C-787C8CAF65ED}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE