Skip to content

Commit 4f883b6

Browse files
authored
Make culture specific update dates work again (#19145)
1 parent 9b81bf5 commit 4f883b6

File tree

8 files changed

+187
-19
lines changed

8 files changed

+187
-19
lines changed

src/Umbraco.Core/Models/ContentBase.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ public void SetCultureName(string? name, string? culture)
301301
}
302302

303303
// set
304-
else
304+
else if (GetCultureName(culture) != name)
305305
{
306306
this.SetCultureInfo(culture!, name, DateTime.Now);
307307
}
@@ -455,10 +455,12 @@ public void SetValue(string propertyTypeAlias, object? value, string? culture =
455455
$"No PropertyType exists with the supplied alias \"{propertyTypeAlias}\".");
456456
}
457457

458-
property?.SetValue(value, culture, segment);
459-
460-
// bump the culture to be flagged for updating
461-
this.TouchCulture(culture);
458+
var updated = property.SetValue(value, culture, segment);
459+
if (updated)
460+
{
461+
// bump the culture to be flagged for updating
462+
this.TouchCulture(culture);
463+
}
462464
}
463465

464466
/// <inheritdoc />

src/Umbraco.Core/Models/Entities/BeingDirtyBase.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ protected void SetPropertyValueAndDetectChanges<T>(T? value, ref T? valueRef, st
162162
/// <param name="propertyName">The property name.</param>
163163
/// <param name="comparer">A comparer to compare property values.</param>
164164
/// <param name="changed">A value indicating whether we know values have changed and no comparison is required.</param>
165-
protected void DetectChanges<T>(T value, T orig, string propertyName, IEqualityComparer<T> comparer, bool changed)
165+
/// <returns>True if a change was detected, false otherwise.</returns>
166+
protected bool DetectChanges<T>(T value, T orig, string propertyName, IEqualityComparer<T> comparer, bool changed)
166167
{
167168
// compare values
168169
changed = _withChanges && (changed || !comparer.Equals(orig, value));
@@ -172,6 +173,8 @@ protected void DetectChanges<T>(T value, T orig, string propertyName, IEqualityC
172173
{
173174
OnPropertyChanged(propertyName);
174175
}
176+
177+
return changed;
175178
}
176179

177180
#endregion

src/Umbraco.Core/Models/IProperty.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,12 @@ public interface IProperty : IEntity, IRememberBeingDirty
3232
/// <summary>
3333
/// Sets a value.
3434
/// </summary>
35-
void SetValue(object? value, string? culture = null, string? segment = null);
35+
/// <returns>true if the value was set (updated), false otherwise.</returns>
36+
/// <remarks>
37+
/// A false return value does not indicate failure, but rather that the property value was not changed
38+
/// (i.e. the value passed in was equal to the current property value).
39+
/// </remarks>
40+
bool SetValue(object? value, string? culture = null, string? segment = null);
3641

3742
void PublishValues(string? culture = "*", string segment = "*");
3843

src/Umbraco.Core/Models/Property.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,8 @@ public void UnpublishValues(string? culture = "*", string? segment = "*")
257257
}
258258
}
259259

260-
/// <summary>
261-
/// Sets a value.
262-
/// </summary>
263-
public void SetValue(object? value, string? culture = null, string? segment = null)
260+
/// <inheritdoc />
261+
public bool SetValue(object? value, string? culture = null, string? segment = null)
264262
{
265263
culture = culture?.NullOrWhiteSpaceAsNull();
266264
segment = segment?.NullOrWhiteSpaceAsNull();
@@ -273,15 +271,18 @@ public void SetValue(object? value, string? culture = null, string? segment = nu
273271

274272
(IPropertyValue? pvalue, var change) = GetPValue(culture, segment, true);
275273

274+
var changed = false;
276275
if (pvalue is not null)
277276
{
278277
var origValue = pvalue.EditedValue;
279278
var setValue = ConvertAssignedValue(value);
280279

281280
pvalue.EditedValue = setValue;
282281

283-
DetectChanges(setValue, origValue, nameof(Values), PropertyValueComparer, change);
282+
changed = DetectChanges(setValue, origValue, nameof(Values), PropertyValueComparer, change);
284283
}
284+
285+
return changed;
285286
}
286287

287288
public object? ConvertAssignedValue(object? value) =>

tests/Umbraco.Tests.Integration/Umbraco.Core/Services/ContentPublishingServiceTests.Publish.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,45 @@ public async Task Can_Publish_Culture_With_Unpublished_Parent_Culture()
347347
Constants.Security.SuperUserKey);
348348
Assert.IsTrue(publishAttempt.Success);
349349
}
350+
351+
[Test]
352+
public async Task Republishing_Single_Culture_Does_Not_Change_Publish_Or_Update_Date_For_Other_Cultures()
353+
{
354+
var (langEn, langDa, langBe, contentType) = await SetupVariantDoctypeAsync();
355+
var setupData = await CreateVariantContentAsync(langEn, langDa, langBe, contentType);
356+
357+
var publishAttempt = await ContentPublishingService.PublishAsync(
358+
setupData.Key,
359+
[
360+
new() { Culture = langEn.IsoCode },
361+
new() { Culture = langDa.IsoCode },
362+
new() { Culture = langBe.IsoCode },
363+
],
364+
Constants.Security.SuperUserKey);
365+
Assert.IsTrue(publishAttempt.Success);
366+
367+
var content = ContentService.GetById(setupData.Key)!;
368+
var firstPublishDateEn = content.GetPublishDate(langEn.IsoCode)
369+
?? throw new InvalidOperationException("Expected a publish date for EN");
370+
var firstPublishDateDa = content.GetPublishDate(langDa.IsoCode)
371+
?? throw new InvalidOperationException("Expected a publish date for DA");
372+
var firstPublishDateBe = content.GetPublishDate(langBe.IsoCode)
373+
?? throw new InvalidOperationException("Expected a publish date for BE");
374+
375+
Thread.Sleep(100);
376+
377+
publishAttempt = await ContentPublishingService.PublishAsync(
378+
content.Key,
379+
[new() { Culture = langEn.IsoCode }],
380+
Constants.Security.SuperUserKey);
381+
Assert.IsTrue(publishAttempt.Success);
382+
383+
content = ContentService.GetById(content.Key)!;
384+
Assert.AreEqual(firstPublishDateDa, content.GetPublishDate(langDa.IsoCode));
385+
Assert.AreEqual(firstPublishDateBe, content.GetPublishDate(langBe.IsoCode));
386+
387+
var lastPublishDateEn = content.GetPublishDate(langEn.IsoCode)
388+
?? throw new InvalidOperationException("Expected a publish date for EN");
389+
Assert.Greater(lastPublishDateEn, firstPublishDateEn);
390+
}
350391
}

tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTests.Update.cs

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,4 +567,115 @@ public async Task Cannot_Update_Variant_Readonly_Property_Value()
567567
Assert.AreEqual("The initial Danish label value", content.GetValue<string>("variantLabel", "da-DK"));
568568
});
569569
}
570+
571+
[Test]
572+
public async Task Updating_Single_Variant_Name_Does_Not_Change_Update_Dates_Of_Other_Vaiants()
573+
{
574+
var contentType = await CreateVariantContentType(variantTitleAsMandatory: false);
575+
576+
var createModel = new ContentCreateModel
577+
{
578+
ContentTypeKey = contentType.Key,
579+
ParentKey = Constants.System.RootKey,
580+
Properties = [],
581+
Variants =
582+
[
583+
new VariantModel { Culture = "en-US", Name = "Initial English Name" },
584+
new VariantModel { Culture = "da-DK", Name = "Initial Danish Name" }
585+
],
586+
};
587+
588+
var createResult = await ContentEditingService.CreateAsync(createModel, Constants.Security.SuperUserKey);
589+
Assert.IsTrue(createResult.Success);
590+
591+
var firstUpdateDateEn = createResult.Result.Content!.GetUpdateDate("en-US")!;
592+
var firstUpdateDateDa = createResult.Result.Content!.GetUpdateDate("da-DK")!;
593+
594+
Thread.Sleep(100);
595+
596+
var updateModel = new ContentUpdateModel
597+
{
598+
Properties = [],
599+
Variants =
600+
[
601+
new VariantModel { Culture = "en-US", Name = "Updated English Name" },
602+
new VariantModel { Culture = "da-DK", Name = "Initial Danish Name" }
603+
]
604+
};
605+
606+
var updateResult = await ContentEditingService.UpdateAsync(createResult.Result.Content.Key, updateModel, Constants.Security.SuperUserKey);
607+
Assert.IsTrue(updateResult.Success);
608+
Assert.AreEqual(ContentEditingOperationStatus.Success, updateResult.Status);
609+
VerifyUpdate(updateResult.Result.Content);
610+
611+
// re-get and re-test
612+
VerifyUpdate(await ContentEditingService.GetAsync(updateResult.Result.Content!.Key));
613+
614+
void VerifyUpdate(IContent? updatedContent)
615+
{
616+
Assert.IsNotNull(updatedContent);
617+
Assert.AreEqual(firstUpdateDateDa, updatedContent.GetUpdateDate("da-DK"));
618+
619+
var lastUpdateDateEn = updatedContent.GetUpdateDate("en-US")
620+
?? throw new InvalidOperationException("Expected a publish date for EN");
621+
Assert.Greater(lastUpdateDateEn, firstUpdateDateEn);
622+
}
623+
}
624+
625+
[Test]
626+
public async Task Updating_Single_Variant_Property_Does_Not_Change_Update_Dates_Of_Other_Variants()
627+
{
628+
var content = await CreateCultureVariantContent();
629+
var firstUpdateDateEn = content.GetUpdateDate("en-US")
630+
?? throw new InvalidOperationException("Expected an update date for EN");
631+
var firstUpdateDateDa = content.GetUpdateDate("da-DK")
632+
?? throw new InvalidOperationException("Expected an update date for DA");
633+
634+
var updateModel = new ContentUpdateModel
635+
{
636+
Properties =
637+
[
638+
new PropertyValueModel
639+
{
640+
Alias = "invariantTitle",
641+
Value = "The invariant title"
642+
},
643+
new PropertyValueModel
644+
{
645+
Culture = "en-US",
646+
Alias = "variantTitle",
647+
Value = content.GetValue<string>("variantTitle", "en-US")!
648+
},
649+
new PropertyValueModel
650+
{
651+
Culture = "da-DK",
652+
Alias = "variantTitle",
653+
Value = "The updated Danish title"
654+
}
655+
],
656+
Variants =
657+
[
658+
new VariantModel { Culture = "en-US", Name = content.GetCultureName("en-US")! },
659+
new VariantModel { Culture = "da-DK", Name = content.GetCultureName("da-DK")! }
660+
]
661+
};
662+
663+
var result = await ContentEditingService.UpdateAsync(content.Key, updateModel, Constants.Security.SuperUserKey);
664+
Assert.IsTrue(result.Success);
665+
Assert.AreEqual(ContentEditingOperationStatus.Success, result.Status);
666+
VerifyUpdate(result.Result.Content);
667+
668+
// re-get and re-test
669+
VerifyUpdate(await ContentEditingService.GetAsync(content.Key));
670+
671+
void VerifyUpdate(IContent? updatedContent)
672+
{
673+
Assert.IsNotNull(updatedContent);
674+
Assert.AreEqual(firstUpdateDateEn, updatedContent.GetUpdateDate("en-US"));
675+
676+
var lastUpdateDateDa = updatedContent.GetUpdateDate("da-DK")
677+
?? throw new InvalidOperationException("Expected an update date for DA");
678+
Assert.Greater(lastUpdateDateDa, firstUpdateDateDa);
679+
}
680+
}
570681
}

tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentEditingServiceTestsBase.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ protected IContentType CreateInvariantContentType(params ITemplate[] templates)
7474
return contentType;
7575
}
7676

77-
protected async Task<IContentType> CreateVariantContentType(ContentVariation variation = ContentVariation.Culture)
77+
protected async Task<IContentType> CreateVariantContentType(ContentVariation variation = ContentVariation.Culture, bool variantTitleAsMandatory = true)
7878
{
7979
var language = new LanguageBuilder()
8080
.WithCultureInfo("da-DK")
@@ -88,7 +88,7 @@ protected async Task<IContentType> CreateVariantContentType(ContentVariation var
8888
.AddPropertyType()
8989
.WithAlias("variantTitle")
9090
.WithName("Variant Title")
91-
.WithMandatory(true)
91+
.WithMandatory(variantTitleAsMandatory)
9292
.WithVariations(variation)
9393
.Done()
9494
.AddPropertyType()

tests/Umbraco.Tests.UnitTests/Umbraco.Core/Models/ContentTests.cs

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ public class ContentTests
2929

3030
private readonly PropertyEditorCollection _propertyEditorCollection = new (new DataEditorCollection(() => []));
3131

32-
[Test]
33-
public void Variant_Culture_Names_Track_Dirty_Changes()
32+
[TestCase("name-fr", false)]
33+
[TestCase("name-fr-updated", true)]
34+
public void Variant_Culture_Names_Track_Dirty_Changes(string newName, bool expectedDirty)
3435
{
3536
var contentType = new ContentTypeBuilder()
3637
.WithAlias("contentType")
@@ -58,14 +59,17 @@ public void Variant_Culture_Names_Track_Dirty_Changes()
5859
Assert.IsTrue(frCultureName.IsPropertyDirty("Date"));
5960

6061
content.ResetDirtyProperties();
62+
frCultureName.ResetDirtyProperties();
6163

6264
Assert.IsFalse(content.IsPropertyDirty("CultureInfos")); // it's been reset
6365
Assert.IsTrue(content.WasPropertyDirty("CultureInfos"));
6466

6567
Thread.Sleep(500); // The "Date" wont be dirty if the test runs too fast since it will be the same date
66-
content.SetCultureName("name-fr", langFr);
67-
Assert.IsTrue(frCultureName.IsPropertyDirty("Date"));
68-
Assert.IsTrue(content.IsPropertyDirty("CultureInfos")); // it's true now since we've updated a name
68+
content.SetCultureName(newName, langFr);
69+
70+
// dirty is only true if we updated the name
71+
Assert.AreEqual(expectedDirty, frCultureName.IsPropertyDirty("Date"));
72+
Assert.AreEqual(expectedDirty, content.IsPropertyDirty("CultureInfos"));
6973
}
7074

7175
[Test]
@@ -97,6 +101,7 @@ public void Variant_Published_Culture_Names_Track_Dirty_Changes()
97101
Assert.IsTrue(frCultureName.IsPropertyDirty("Date"));
98102

99103
content.ResetDirtyProperties();
104+
frCultureName.ResetDirtyProperties();
100105

101106
Assert.IsFalse(content.IsPropertyDirty("PublishCultureInfos")); // it's been reset
102107
Assert.IsTrue(content.WasPropertyDirty("PublishCultureInfos"));

0 commit comments

Comments
 (0)