Skip to content

Commit b3d580d

Browse files
Merge branch 'v11/dev' into contrib
2 parents 99049c2 + 3caa2a3 commit b3d580d

File tree

22 files changed

+231
-168
lines changed

22 files changed

+231
-168
lines changed

src/Umbraco.Cms.ManagementApi/DependencyInjection/MappingBuilderExtensions.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using Umbraco.Cms.ManagementApi.Mapping.Languages;
77
using Umbraco.Cms.ManagementApi.Mapping.Relation;
88
using Umbraco.Cms.ManagementApi.Mapping.TrackedReferences;
9-
using Umbraco.New.Cms.Infrastructure.Persistence.Mappers;
109

1110
namespace Umbraco.Cms.ManagementApi.DependencyInjection;
1211

@@ -17,7 +16,6 @@ internal static IUmbracoBuilder AddMappers(this IUmbracoBuilder builder)
1716
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
1817
.Add<DictionaryViewModelMapDefinition>()
1918
.Add<TrackedReferenceViewModelsMapDefinition>()
20-
.Add<RelationModelMapDefinition>()
2119
.Add<RelationViewModelsMapDefinition>()
2220
.Add<LanguageViewModelsMapDefinition>()
2321
.Add<InstallerViewModelsMapDefinition>()

src/Umbraco.Core/Security/AuthenticationExtensions.cs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,56 @@
77

88
namespace Umbraco.Extensions;
99

10+
/// <summary>
11+
/// Extension methods for <see cref="IIdentity" />.
12+
/// </summary>
1013
public static class AuthenticationExtensions
1114
{
1215
/// <summary>
13-
/// Ensures that the thread culture is set based on the back office user's culture
16+
/// Ensures that the thread culture is set based on the back office user's culture.
1417
/// </summary>
18+
/// <param name="identity">The identity.</param>
1519
public static void EnsureCulture(this IIdentity identity)
1620
{
1721
CultureInfo? culture = GetCulture(identity);
18-
if (!(culture is null))
22+
if (culture is not null)
1923
{
2024
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture = culture;
2125
}
2226
}
2327

28+
/// <summary>
29+
/// Gets the culture string from the back office user.
30+
/// </summary>
31+
/// <param name="identity">The identity.</param>
32+
/// <returns>
33+
/// The culture string.
34+
/// </returns>
35+
public static string? GetCultureString(this IIdentity identity)
36+
{
37+
if (identity is ClaimsIdentity umbIdentity &&
38+
umbIdentity.VerifyBackOfficeIdentity(out _) &&
39+
umbIdentity.IsAuthenticated)
40+
{
41+
return umbIdentity.GetCultureString();
42+
}
43+
44+
return null;
45+
}
46+
47+
/// <summary>
48+
/// Gets the culture from the back office user.
49+
/// </summary>
50+
/// <param name="identity">The identity.</param>
51+
/// <returns>
52+
/// The culture.
53+
/// </returns>
2454
public static CultureInfo? GetCulture(this IIdentity identity)
2555
{
26-
if (identity is ClaimsIdentity umbIdentity && umbIdentity.VerifyBackOfficeIdentity(out _) &&
27-
umbIdentity.IsAuthenticated && umbIdentity.GetCultureString() is not null)
56+
string? culture = identity.GetCultureString();
57+
if (!string.IsNullOrEmpty(culture))
2858
{
29-
return CultureInfo.GetCultureInfo(umbIdentity.GetCultureString()!);
59+
return CultureInfo.GetCultureInfo(culture);
3060
}
3161

3262
return null;

src/Umbraco.Core/Services/ContentService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,8 @@ public PublishResult SaveAndPublish(IContent content, string culture = "*", int
11461146

11471147
var allLangs = _languageRepository.GetMany().ToList();
11481148

1149+
// Change state to publishing
1150+
content.PublishedState = PublishedState.Publishing;
11491151
var savingNotification = new ContentSavingNotification(content, evtMsgs);
11501152
if (scope.Notifications.PublishCancelable(savingNotification))
11511153
{

src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.MappingProfiles.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Umbraco.Cms.Core.Mapping;
44
using Umbraco.Cms.Core.Models.Mapping;
55
using Umbraco.Cms.Core.Security;
6+
using Umbraco.Cms.Infrastructure.Mapping;
67
using Umbraco.Extensions;
78

89
namespace Umbraco.Cms.Infrastructure.DependencyInjection;
@@ -23,6 +24,7 @@ public static IUmbracoBuilder AddCoreMappingProfiles(this IUmbracoBuilder builde
2324
.Add<ContentTypeMapDefinition>()
2425
.Add<DataTypeMapDefinition>()
2526
.Add<EntityMapDefinition>()
27+
.Add<RelationModelMapDefinition>()
2628
.Add<DictionaryMapDefinition>()
2729
.Add<MacroMapDefinition>()
2830
.Add<RedirectUrlMapDefinition>()
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using Umbraco.Cms.Core.Models;
33
using Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement;
44

5-
namespace Umbraco.New.Cms.Infrastructure.Persistence.Mappers;
5+
namespace Umbraco.Cms.Infrastructure.Mapping;
66

77
public class RelationModelMapDefinition : IMapDefinition
88
{

src/Umbraco.Infrastructure/PropertyEditors/NestedPropertyIndexValueFactoryBase.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ protected NestedPropertyIndexValueFactoryBase(
4040
contentType
4141
.PropertyGroups
4242
.SelectMany(x => x.PropertyTypes!)
43+
.Select(propertyType =>
44+
{
45+
// We want to ensure that the nested properties are set vary by culture if the parent is
46+
// This is because it's perfectly valid to have a nested property type that's set to invariant even if the parent varies.
47+
// For instance in a block list, the list it self can vary, but the elements can be invariant, at the same time.
48+
if (culture is not null)
49+
{
50+
propertyType.Variations |= ContentVariation.Culture;
51+
}
52+
53+
if (segment is not null)
54+
{
55+
propertyType.Variations |= ContentVariation.Segment;
56+
}
57+
58+
return propertyType;
59+
})
4360
.ToDictionary(x => x.Alias);
4461

4562
result.AddRange(GetNestedResults(
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
using System.Globalization;
2+
using Microsoft.AspNetCore.Builder;
3+
using Microsoft.AspNetCore.Http;
4+
using Microsoft.AspNetCore.Localization;
5+
using Microsoft.Extensions.Primitives;
6+
7+
namespace Umbraco.Cms.Web.Common.Localization;
8+
9+
/// <summary>
10+
/// Base implementation that dynamically adds the determined cultures to the supported cultures.
11+
/// </summary>
12+
public abstract class DynamicRequestCultureProviderBase : RequestCultureProvider
13+
{
14+
private readonly RequestLocalizationOptions _options;
15+
private readonly object _lockerSupportedCultures = new();
16+
private readonly object _lockerSupportedUICultures = new();
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="DynamicRequestCultureProviderBase" /> class.
20+
/// </summary>
21+
/// <param name="requestLocalizationOptions">The request localization options.</param>
22+
protected DynamicRequestCultureProviderBase(RequestLocalizationOptions requestLocalizationOptions)
23+
=> _options = Options = requestLocalizationOptions;
24+
25+
/// <inheritdoc />
26+
public override Task<ProviderCultureResult?> DetermineProviderCultureResult(HttpContext httpContext)
27+
{
28+
ProviderCultureResult? result = GetProviderCultureResult(httpContext);
29+
if (result is not null)
30+
{
31+
// We need to dynamically change the supported cultures since we won't ever know what languages are used since
32+
// they are dynamic within Umbraco. We have to handle this for both UI and Region cultures, in case people run different region and UI languages
33+
// This code to check existence is borrowed from aspnetcore to avoid creating a CultureInfo
34+
// https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165
35+
TryAddLocked(_options.SupportedCultures, result.Cultures, _lockerSupportedCultures, (culture) =>
36+
{
37+
_options.SupportedCultures ??= new List<CultureInfo>();
38+
_options.SupportedCultures.Add(CultureInfo.GetCultureInfo(culture.ToString()));
39+
});
40+
41+
TryAddLocked(_options.SupportedUICultures, result.UICultures, _lockerSupportedUICultures, (culture) =>
42+
{
43+
_options.SupportedUICultures ??= new List<CultureInfo>();
44+
_options.SupportedUICultures.Add(CultureInfo.GetCultureInfo(culture.ToString()));
45+
});
46+
47+
return Task.FromResult<ProviderCultureResult?>(result);
48+
}
49+
50+
return NullProviderCultureResult;
51+
}
52+
53+
/// <summary>
54+
/// Gets the provider culture result.
55+
/// </summary>
56+
/// <param name="httpContext">The HTTP context.</param>
57+
/// <returns>
58+
/// The provider culture result.
59+
/// </returns>
60+
protected abstract ProviderCultureResult? GetProviderCultureResult(HttpContext httpContext);
61+
62+
/// <summary>
63+
/// Executes the <paramref name="addAction" /> within a double-checked lock when the a culture in <paramref name="cultures" /> does not exist in <paramref name="supportedCultures" />.
64+
/// </summary>
65+
/// <param name="supportedCultures">The supported cultures.</param>
66+
/// <param name="cultures">The cultures.</param>
67+
/// <param name="locker">The locker object to use.</param>
68+
/// <param name="addAction">The add action to execute.</param>
69+
private static void TryAddLocked(IEnumerable<CultureInfo>? supportedCultures, IEnumerable<StringSegment> cultures, object locker, Action<StringSegment> addAction)
70+
{
71+
foreach (StringSegment culture in cultures)
72+
{
73+
Func<CultureInfo, bool> predicate = x => culture.Equals(x.Name, StringComparison.OrdinalIgnoreCase);
74+
if (supportedCultures?.Any(predicate) is not true)
75+
{
76+
lock (locker)
77+
{
78+
if (supportedCultures?.Any(predicate) is not true)
79+
{
80+
addAction(culture);
81+
}
82+
}
83+
}
84+
}
85+
}
86+
}
Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Umbraco.
22
// See LICENSE for more details.
33

4-
using System.Globalization;
54
using Microsoft.AspNetCore.Builder;
65
using Microsoft.AspNetCore.Http;
76
using Microsoft.AspNetCore.Localization;
@@ -10,50 +9,21 @@
109
namespace Umbraco.Cms.Web.Common.Localization;
1110

1211
/// <summary>
13-
/// Sets the request culture to the culture of the back office user if one is determined to be in the request
12+
/// Sets the request culture to the culture of the back office user, if one is determined to be in the request.
1413
/// </summary>
15-
public class UmbracoBackOfficeIdentityCultureProvider : RequestCultureProvider
14+
public class UmbracoBackOfficeIdentityCultureProvider : DynamicRequestCultureProviderBase
1615
{
17-
private readonly RequestLocalizationOptions _localizationOptions;
18-
private readonly object _locker = new();
19-
2016
/// <summary>
21-
/// Initializes a new instance of the <see cref="UmbracoBackOfficeIdentityCultureProvider" /> class.
17+
/// Initializes a new instance of the <see cref="UmbracoBackOfficeIdentityCultureProvider" /> class.
2218
/// </summary>
23-
public UmbracoBackOfficeIdentityCultureProvider(RequestLocalizationOptions localizationOptions) =>
24-
_localizationOptions = localizationOptions;
19+
/// <param name="localizationOptions">The localization options.</param>
20+
public UmbracoBackOfficeIdentityCultureProvider(RequestLocalizationOptions localizationOptions)
21+
: base(localizationOptions)
22+
{ }
2523

2624
/// <inheritdoc />
27-
public override Task<ProviderCultureResult?> DetermineProviderCultureResult(HttpContext httpContext)
28-
{
29-
CultureInfo? culture = httpContext.User.Identity?.GetCulture();
30-
31-
if (culture is null)
32-
{
33-
return NullProviderCultureResult;
34-
}
35-
36-
lock (_locker)
37-
{
38-
// We need to dynamically change the supported cultures since we won't ever know what languages are used since
39-
// they are dynamic within Umbraco. We have to handle this for both UI and Region cultures, in case people run different region and UI languages
40-
var cultureExists = _localizationOptions.SupportedCultures?.Contains(culture) ?? false;
41-
42-
if (!cultureExists)
43-
{
44-
// add this as a supporting culture
45-
_localizationOptions.SupportedCultures?.Add(culture);
46-
}
47-
48-
var uiCultureExists = _localizationOptions.SupportedCultures?.Contains(culture) ?? false;
49-
50-
if (!uiCultureExists)
51-
{
52-
// add this as a supporting culture
53-
_localizationOptions.SupportedUICultures?.Add(culture);
54-
}
55-
56-
return Task.FromResult<ProviderCultureResult?>(new ProviderCultureResult(culture.Name));
57-
}
58-
}
25+
protected sealed override ProviderCultureResult? GetProviderCultureResult(HttpContext httpContext)
26+
=> httpContext.User.Identity?.GetCultureString() is string culture
27+
? new ProviderCultureResult(culture)
28+
: null;
5929
}
Lines changed: 11 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,27 @@
1-
using System.Globalization;
21
using Microsoft.AspNetCore.Builder;
32
using Microsoft.AspNetCore.Http;
43
using Microsoft.AspNetCore.Localization;
5-
using Microsoft.Extensions.Primitives;
64
using Umbraco.Cms.Core.Routing;
75
using Umbraco.Cms.Web.Common.Routing;
86

97
namespace Umbraco.Cms.Web.Common.Localization;
108

119
/// <summary>
12-
/// Sets the request culture to the culture of the <see cref="IPublishedRequest" /> if one is found in the request
10+
/// Sets the request culture to the culture of the <see cref="IPublishedRequest" />, if one is found in the request.
1311
/// </summary>
14-
public class UmbracoPublishedContentCultureProvider : RequestCultureProvider
12+
public class UmbracoPublishedContentCultureProvider : DynamicRequestCultureProviderBase
1513
{
16-
private readonly RequestLocalizationOptions _localizationOptions;
17-
private readonly object _locker = new();
18-
1914
/// <summary>
20-
/// Initializes a new instance of the <see cref="UmbracoPublishedContentCultureProvider" /> class.
15+
/// Initializes a new instance of the <see cref="UmbracoPublishedContentCultureProvider" /> class.
2116
/// </summary>
22-
public UmbracoPublishedContentCultureProvider(RequestLocalizationOptions localizationOptions) =>
23-
_localizationOptions = localizationOptions;
17+
/// <param name="localizationOptions">The localization options.</param>
18+
public UmbracoPublishedContentCultureProvider(RequestLocalizationOptions localizationOptions)
19+
: base(localizationOptions)
20+
{ }
2421

2522
/// <inheritdoc />
26-
public override Task<ProviderCultureResult?> DetermineProviderCultureResult(HttpContext httpContext)
27-
{
28-
UmbracoRouteValues? routeValues = httpContext.Features.Get<UmbracoRouteValues>();
29-
if (routeValues != null)
30-
{
31-
var culture = routeValues.PublishedRequest.Culture;
32-
if (culture != null)
33-
{
34-
lock (_locker)
35-
{
36-
// We need to dynamically change the supported cultures since we won't ever know what languages are used since
37-
// they are dynamic within Umbraco. We have to handle this for both UI and Region cultures, in case people run different region and UI languages
38-
// This code to check existence is borrowed from aspnetcore to avoid creating a CultureInfo
39-
// https://github.com/dotnet/aspnetcore/blob/b795ac3546eb3e2f47a01a64feb3020794ca33bb/src/Middleware/Localization/src/RequestLocalizationMiddleware.cs#L165
40-
CultureInfo? existingCulture = _localizationOptions.SupportedCultures?.FirstOrDefault(
41-
supportedCulture =>
42-
StringSegment.Equals(supportedCulture.Name, culture, StringComparison.OrdinalIgnoreCase));
43-
44-
if (existingCulture == null)
45-
{
46-
// add this as a supporting culture
47-
var ci = CultureInfo.GetCultureInfo(culture);
48-
_localizationOptions.SupportedCultures?.Add(ci);
49-
}
50-
51-
CultureInfo? existingUICulture = _localizationOptions.SupportedUICultures?.FirstOrDefault(
52-
supportedCulture =>
53-
StringSegment.Equals(supportedCulture.Name, culture, StringComparison.OrdinalIgnoreCase));
54-
55-
if (existingUICulture == null)
56-
{
57-
// add this as a supporting culture
58-
var ci = CultureInfo.GetCultureInfo(culture);
59-
_localizationOptions.SupportedUICultures?.Add(ci);
60-
}
61-
}
62-
63-
return Task.FromResult<ProviderCultureResult?>(new ProviderCultureResult(culture));
64-
}
65-
}
66-
67-
return NullProviderCultureResult;
68-
}
23+
protected sealed override ProviderCultureResult? GetProviderCultureResult(HttpContext httpContext)
24+
=> httpContext.Features.Get<UmbracoRouteValues>()?.PublishedRequest.Culture is string culture
25+
? new ProviderCultureResult(culture)
26+
: null;
6927
}

0 commit comments

Comments
 (0)