diff --git a/src/Umbraco.Cms.Api.Delivery/Services/RequestRedirectService.cs b/src/Umbraco.Cms.Api.Delivery/Services/RequestRedirectService.cs index 6189e7154e..188e6db7ef 100644 --- a/src/Umbraco.Cms.Api.Delivery/Services/RequestRedirectService.cs +++ b/src/Umbraco.Cms.Api.Delivery/Services/RequestRedirectService.cs @@ -42,31 +42,42 @@ internal sealed class RequestRedirectService : RoutingServiceBase, IRequestRedir { requestedPath = requestedPath.EnsureStartsWith("/"); + IPublishedContent? startItem = GetStartItem(); + // must append the root content url segment if it is not hidden by config, because // the URL tracking is based on the actual URL, including the root content url segment - if (_globalSettings.HideTopLevelNodeFromPath == false) + if (_globalSettings.HideTopLevelNodeFromPath == false && startItem?.UrlSegment != null) { - IPublishedContent? startItem = GetStartItem(); - if (startItem?.UrlSegment != null) - { - requestedPath = $"{startItem.UrlSegment.EnsureStartsWith("/")}{requestedPath}"; - } + requestedPath = $"{startItem.UrlSegment.EnsureStartsWith("/")}{requestedPath}"; } var culture = _requestCultureService.GetRequestedCulture(); - // append the configured domain content ID to the path if we have a domain bound request, - // because URL tracking registers the tracked url like "{domain content ID}/{content path}" - Uri contentRoute = GetDefaultRequestUri(requestedPath); - DomainAndUri? domainAndUri = GetDomainAndUriForRoute(contentRoute); - if (domainAndUri != null) + // important: redirect URLs are always tracked without trailing slashes + requestedPath = requestedPath.TrimEnd("/"); + IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath, culture); + + // if a redirect URL was not found, try by appending the start item ID because URL tracking might have tracked + // a redirect with "{root content ID}/{content path}" + if (redirectUrl is null && startItem is not null) { - requestedPath = GetContentRoute(domainAndUri, contentRoute); - culture ??= domainAndUri.Culture; + redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl($"{startItem.Id}{requestedPath}", culture); + } + + // still no redirect URL found - try looking for a configured domain if we have a domain bound request, + // because URL tracking might have tracked a redirect with "{domain content ID}/{content path}" + if (redirectUrl is null) + { + Uri contentRoute = GetDefaultRequestUri(requestedPath); + DomainAndUri? domainAndUri = GetDomainAndUriForRoute(contentRoute); + if (domainAndUri is not null) + { + requestedPath = GetContentRoute(domainAndUri, contentRoute); + culture ??= domainAndUri.Culture; + redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath, culture); + } } - // important: redirect URLs are always tracked without trailing slashes - IRedirectUrl? redirectUrl = _redirectUrlService.GetMostRecentRedirectUrl(requestedPath.TrimEnd("/"), culture); IPublishedContent? content = redirectUrl != null ? _apiPublishedContentCache.GetById(redirectUrl.ContentKey) : null; diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs index ba7b251aa0..fa334e5c4a 100644 --- a/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyOptions.cs @@ -11,6 +11,7 @@ public class RepositoryCachePolicyOptions public RepositoryCachePolicyOptions(Func performCount) { PerformCount = performCount; + CacheNullValues = false; GetAllCacheValidateCount = true; GetAllCacheAllowZeroCount = false; } @@ -21,6 +22,7 @@ public class RepositoryCachePolicyOptions public RepositoryCachePolicyOptions() { PerformCount = null; + CacheNullValues = false; GetAllCacheValidateCount = false; GetAllCacheAllowZeroCount = false; } @@ -30,6 +32,11 @@ public class RepositoryCachePolicyOptions /// public Func? PerformCount { get; set; } + /// + /// True if the Get method will cache null results so that the db is not hit for repeated lookups + /// + public bool CacheNullValues { get; set; } + /// /// True/false as to validate the total item count when all items are returned from cache, the default is true but this /// means that a db lookup will occur - though that lookup will probably be significantly less expensive than the diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index b18239298f..6728b2c7a6 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -34,7 +34,7 @@ public class TypeFinder : ITypeFinder "ServiceStack.", "SqlCE4Umbraco,", "Superpower,", // used by Serilog "System.", "TidyNet,", "TidyNet.", "WebDriver,", "itextsharp,", "mscorlib,", "NUnit,", "NUnit.", "NUnit3.", "Selenium.", "ImageProcessor", "MiniProfiler.", "Owin,", "SQLite", - "ReSharperTestRunner", "ReSharperTestRunner32", "ReSharperTestRunner64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension + "ReSharperTestRunner", "ReSharperTestRunner32", "ReSharperTestRunner64", "ReSharperTestRunnerArm32", "ReSharperTestRunnerArm64", // These are used by the Jetbrains Rider IDE and Visual Studio ReSharper Extension }; private static readonly ConcurrentDictionary TypeNamesCache = new(); diff --git a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs index be86cf1f2b..127b7d9330 100644 --- a/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ModelsBuilderSettings.cs @@ -16,6 +16,7 @@ public class ModelsBuilderSettings internal const string StaticModelsDirectory = "~/umbraco/models"; internal const bool StaticAcceptUnsafeModelsDirectory = false; internal const int StaticDebugLevel = 0; + internal const bool StaticIncludeVersionNumberInGeneratedModels = true; private bool _flagOutOfDateModels = true; /// @@ -78,4 +79,16 @@ public class ModelsBuilderSettings /// 0 means minimal (safe on live site), anything else means more and more details (maybe not safe). [DefaultValue(StaticDebugLevel)] public int DebugLevel { get; set; } = StaticDebugLevel; + + /// + /// Gets or sets a value indicating whether the version number should be included in generated models. + /// + /// + /// By default this is written to the output in + /// generated code for each property of the model. This can be useful for debugging purposes but isn't essential, + /// and it has the causes the generated code to change every time Umbraco is upgraded. In turn, this leads + /// to unnecessary code file changes that need to be checked into source control. Default is true. + /// + [DefaultValue(StaticIncludeVersionNumberInGeneratedModels)] + public bool IncludeVersionNumberInGeneratedModels { get; set; } = StaticIncludeVersionNumberInGeneratedModels; } diff --git a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs index 2db42cc3e6..d359f6d208 100644 --- a/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs +++ b/src/Umbraco.Core/Configuration/Models/SecuritySettings.cs @@ -20,6 +20,8 @@ public class SecuritySettings internal const bool StaticAllowEditInvariantFromNonDefault = false; internal const bool StaticAllowConcurrentLogins = false; internal const string StaticAuthCookieName = "UMB_UCONTEXT"; + internal const bool StaticUsernameIsEmail = true; + internal const bool StaticMemberRequireUniqueEmail = true; internal const string StaticAllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+\\"; @@ -64,7 +66,14 @@ public class SecuritySettings /// /// Gets or sets a value indicating whether the user's email address is to be considered as their username. /// - public bool UsernameIsEmail { get; set; } = true; + [DefaultValue(StaticUsernameIsEmail)] + public bool UsernameIsEmail { get; set; } = StaticUsernameIsEmail; + + /// + /// Gets or sets a value indicating whether the member's email address must be unique. + /// + [DefaultValue(StaticMemberRequireUniqueEmail)] + public bool MemberRequireUniqueEmail { get; set; } = StaticMemberRequireUniqueEmail; /// /// Gets or sets the set of allowed characters for a username diff --git a/src/Umbraco.Core/EmbeddedResources/Snippets/LoginStatus.cshtml b/src/Umbraco.Core/EmbeddedResources/Snippets/LoginStatus.cshtml index bbe6192908..a210a06558 100644 --- a/src/Umbraco.Core/EmbeddedResources/Snippets/LoginStatus.cshtml +++ b/src/Umbraco.Core/EmbeddedResources/Snippets/LoginStatus.cshtml @@ -5,7 +5,7 @@ @using Umbraco.Extensions @{ - var isLoggedIn = Context.User?.Identity?.IsAuthenticated ?? false; + var isLoggedIn = Context.User.GetMemberIdentity()?.IsAuthenticated ?? false; var logoutModel = new PostRedirectModel(); // You can modify this to redirect to a different URL instead of the current one logoutModel.RedirectUrl = null; @@ -15,7 +15,7 @@ {