1- using Microsoft . Extensions . DependencyInjection ;
2- using Microsoft . Extensions . Logging ;
3- using Umbraco . Cms . Core . DependencyInjection ;
1+ using Microsoft . Extensions . Logging ;
2+ using Microsoft . Extensions . Options ;
3+ using Umbraco . Cms . Core . Configuration . Models ;
44using Umbraco . Cms . Core . Models ;
55using Umbraco . Cms . Core . Models . ContentEditing ;
66using Umbraco . Cms . Core . Models . Membership ;
@@ -14,42 +14,13 @@ namespace Umbraco.Cms.Core.Services;
1414internal sealed class ContentEditingService
1515 : ContentEditingServiceWithSortingBase < IContent , IContentType , IContentService , IContentTypeService > , IContentEditingService
1616{
17+ private readonly PropertyEditorCollection _propertyEditorCollection ;
1718 private readonly ITemplateService _templateService ;
1819 private readonly ILogger < ContentEditingService > _logger ;
1920 private readonly IUserService _userService ;
2021 private readonly ILocalizationService _localizationService ;
2122 private readonly ILanguageService _languageService ;
22-
23- [ Obsolete ( "Use non-obsolete constructor. This will be removed in Umbraco 16." ) ]
24- public ContentEditingService (
25- IContentService contentService ,
26- IContentTypeService contentTypeService ,
27- PropertyEditorCollection propertyEditorCollection ,
28- IDataTypeService dataTypeService ,
29- ITemplateService templateService ,
30- ILogger < ContentEditingService > logger ,
31- ICoreScopeProvider scopeProvider ,
32- IUserIdKeyResolver userIdKeyResolver ,
33- ITreeEntitySortingService treeEntitySortingService ,
34- IContentValidationService contentValidationService )
35- : this (
36- contentService ,
37- contentTypeService ,
38- propertyEditorCollection ,
39- dataTypeService ,
40- templateService ,
41- logger ,
42- scopeProvider ,
43- userIdKeyResolver ,
44- treeEntitySortingService ,
45- contentValidationService ,
46- StaticServiceProvider . Instance . GetRequiredService < IUserService > ( ) ,
47- StaticServiceProvider . Instance . GetRequiredService < ILocalizationService > ( ) ,
48- StaticServiceProvider . Instance . GetRequiredService < ILanguageService > ( )
49- )
50- {
51-
52- }
23+ private readonly ContentSettings _contentSettings ;
5324
5425 public ContentEditingService (
5526 IContentService contentService ,
@@ -64,14 +35,17 @@ public ContentEditingService(
6435 IContentValidationService contentValidationService ,
6536 IUserService userService ,
6637 ILocalizationService localizationService ,
67- ILanguageService languageService )
38+ ILanguageService languageService ,
39+ IOptions < ContentSettings > contentSettings )
6840 : base ( contentService , contentTypeService , propertyEditorCollection , dataTypeService , logger , scopeProvider , userIdKeyResolver , contentValidationService , treeEntitySortingService )
6941 {
42+ _propertyEditorCollection = propertyEditorCollection ;
7043 _templateService = templateService ;
7144 _logger = logger ;
7245 _userService = userService ;
7346 _localizationService = localizationService ;
7447 _languageService = languageService ;
48+ _contentSettings = contentSettings . Value ;
7549 }
7650
7751 public async Task < IContent ? > GetAsync ( Guid key )
@@ -154,28 +128,53 @@ private async Task<IContent> EnsureOnlyAllowedFieldsAreUpdated(IContent contentW
154128
155129 var allowedCultures = ( await _languageService . GetIsoCodesByIdsAsync ( allowedLanguageIds ) ) . ToHashSet ( ) ;
156130
131+ ILanguage ? defaultLanguage = await _languageService . GetDefaultLanguageAsync ( ) ;
132+
157133 foreach ( var culture in contentWithPotentialUnallowedChanges . EditedCultures ?? contentWithPotentialUnallowedChanges . PublishedCultures )
158134 {
159135 if ( allowedCultures . Contains ( culture ) )
160136 {
161137 continue ;
162138 }
163139
164-
165140 // else override the updates values with the original values.
166141 foreach ( IProperty property in contentWithPotentialUnallowedChanges . Properties )
167142 {
168- if ( property . PropertyType . VariesByCulture ( ) is false )
143+ // if the property varies by culture, simply overwrite the edited property value with the current property value
144+ if ( property . PropertyType . VariesByCulture ( ) )
169145 {
146+ var currentValue = existingContent ? . Properties . First ( x => x . Alias == property . Alias ) . GetValue ( culture , null , false ) ;
147+ property . SetValue ( currentValue , culture , null ) ;
170148 continue ;
171149 }
172150
173- var value = existingContent ? . Properties . First ( x=> x . Alias == property . Alias ) . GetValue ( culture , null , false ) ;
174- property . SetValue ( value , culture , null ) ;
151+ // if the property does not vary by culture and the data editor supports variance within invariant property values,
152+ // we need perform a merge between the edited property value and the current property value
153+ if ( _propertyEditorCollection . TryGet ( property . PropertyType . PropertyEditorAlias , out IDataEditor ? dataEditor ) && dataEditor . CanMergePartialPropertyValues ( property . PropertyType ) )
154+ {
155+ var currentValue = existingContent ? . Properties . First ( x => x . Alias == property . Alias ) . GetValue ( null , null , false ) ;
156+ var editedValue = contentWithPotentialUnallowedChanges . Properties . First ( x => x . Alias == property . Alias ) . GetValue ( null , null , false ) ;
157+ var mergedValue = dataEditor . MergePartialPropertyValueForCulture ( currentValue , editedValue , culture ) ;
158+
159+ // If we are not allowed to edit invariant properties, overwrite the edited property value with the current property value.
160+ if ( _contentSettings . AllowEditInvariantFromNonDefault is false && culture == defaultLanguage ? . IsoCode )
161+ {
162+ mergedValue = dataEditor . MergePartialPropertyValueForCulture ( currentValue , mergedValue , null ) ;
163+ }
164+
165+ property . SetValue ( mergedValue , null , null ) ;
166+ }
167+
168+ // If property does not support merging, we still need to overwrite if we are not allowed to edit invariant properties.
169+ else if ( _contentSettings . AllowEditInvariantFromNonDefault is false && culture == defaultLanguage ? . IsoCode )
170+ {
171+ var currentValue = existingContent ? . Properties . First ( x => x . Alias == property . Alias ) . GetValue ( null , null , false ) ;
172+ property . SetValue ( currentValue , null , null ) ;
173+ }
175174 }
176175 }
177176
178- return contentWithPotentialUnallowedChanges ;
177+ return contentWithPotentialUnallowedChanges ;
179178 }
180179
181180 public async Task < Attempt < ContentUpdateResult , ContentEditingOperationStatus > > UpdateAsync ( Guid key , ContentUpdateModel updateModel , Guid userKey )
0 commit comments