diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs index 1ed8404f78..4c9b19f1a9 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/DocumentRepository.cs @@ -553,6 +553,7 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IContent entity) { var isEntityDirty = entity.IsDirty(); + var editedSnapshot = entity.Edited; // check if we need to make any database changes at all if ((entity.PublishedState == PublishedState.Published || entity.PublishedState == PublishedState.Unpublished) @@ -659,6 +660,19 @@ namespace Umbraco.Cms.Infrastructure.Persistence.Repositories.Implement edited = true; } + // To establish the new value of "edited" we compare all properties publishedValue to editedValue and look + // for differences. + // + // If we SaveAndPublish but the publish fails (e.g. already scheduled for release) + // we have lost the publishedValue on IContent (in memory vs database) so we cannot correctly make that comparison. + // + // This is a slight change to behaviour, historically a publish, followed by change & save, followed by undo change & save + // would change edited back to false. + if (!publishing && editedSnapshot) + { + edited = true; + } + if (entity.ContentType.VariesByCulture()) { // names also impact 'edited' diff --git a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs index d75d667b1c..ad189cc02a 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Infrastructure/Services/ContentServiceTests.cs @@ -1348,6 +1348,107 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services Assert.AreEqual(PublishResultType.FailedPublishAwaitingRelease, published.Result); } + [Test] + public void Failed_Publish_Should_Not_Update_Edited_State_When_Edited_True() + { + // Arrange + IContentService contentService = GetRequiredService(); + IContentTypeService contentTypeService = GetRequiredService(); + + IContentType contentType = new ContentTypeBuilder() + .WithId(0) + .AddPropertyType() + .WithAlias("header") + .WithValueStorageType(ValueStorageType.Integer) + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithName("header") + .Done() + .WithContentVariation(ContentVariation.Nothing) + .Build(); + + contentTypeService.Save(contentType); + + Content content = new ContentBuilder() + .WithId(0) + .WithName("Home") + .WithContentType(contentType) + .AddPropertyData() + .WithKeyValue("header", "Cool header") + .Done() + .Build(); + + contentService.SaveAndPublish(content); + + content.Properties[0].SetValue("Foo", culture: string.Empty); + content.ContentSchedule.Add(DateTime.Now.AddHours(2), null); + contentService.Save(content); + + // Act + var result = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); + + // Assert + Assert.Multiple(() => + { + Assert.IsFalse(result.Success); + Assert.IsTrue(result.Content.Published); + Assert.AreEqual(PublishResultType.FailedPublishAwaitingRelease, result.Result); + + // We changed property data + Assert.IsTrue(result.Content.Edited, "result.Content.Edited"); + }); + } + + // V9 - Tests.Integration + [Test] + public void Failed_Publish_Should_Not_Update_Edited_State_When_Edited_False() + { + // Arrange + IContentService contentService = GetRequiredService(); + IContentTypeService contentTypeService = GetRequiredService(); + + IContentType contentType = new ContentTypeBuilder() + .WithId(0) + .AddPropertyType() + .WithAlias("header") + .WithValueStorageType(ValueStorageType.Integer) + .WithPropertyEditorAlias(Constants.PropertyEditors.Aliases.TextBox) + .WithName("header") + .Done() + .WithContentVariation(ContentVariation.Nothing) + .Build(); + + contentTypeService.Save(contentType); + + Content content = new ContentBuilder() + .WithId(0) + .WithName("Home") + .WithContentType(contentType) + .AddPropertyData() + .WithKeyValue("header", "Cool header") + .Done() + .Build(); + + contentService.SaveAndPublish(content); + + content.ContentSchedule.Add(DateTime.Now.AddHours(2), null); + contentService.Save(content); + + // Act + var result = contentService.SaveAndPublish(content, userId: Constants.Security.SuperUserId); + + // Assert + Assert.Multiple(() => + { + Assert.IsFalse(result.Success); + Assert.IsTrue(result.Content.Published); + Assert.AreEqual(PublishResultType.FailedPublishAwaitingRelease, result.Result); + + // We didn't change any property data + Assert.IsFalse(result.Content.Edited, "result.Content.Edited"); + }); + } + + [Test] public void Cannot_Publish_Culture_Awaiting_Release() { @@ -2151,7 +2252,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services ContentService.Save(rollback2); Assert.IsTrue(rollback2.Published); - Assert.IsFalse(rollback2.Edited); // all changes cleared! + Assert.IsTrue(rollback2.Edited); // Still edited, change of behaviour Assert.AreEqual("Jane Doe", rollback2.GetValue("author")); Assert.AreEqual("Text Page 2 ReReUpdated", rollback2.Name); @@ -2170,7 +2271,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Infrastructure.Services content.CopyFrom(rollto); content.Name = rollto.PublishName; // must do it explicitely AND must pick the publish one! ContentService.Save(content); - Assert.IsFalse(content.Edited); + Assert.IsTrue(content.Edited); //Still edited, change of behaviour Assert.AreEqual("Text Page 2 ReReUpdated", content.Name); Assert.AreEqual("Jane Doe", content.GetValue("author")); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js index 60a4b9245a..9331e4227b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/blocklist/prevalue/blocklist.blockconfiguration.overlay.controller.js @@ -109,7 +109,7 @@ } ] }; - + editorService.contentTypePicker(settingsTypePicker); }); @@ -179,7 +179,7 @@ }, close: () => editorService.close() }; - + editorService.filePicker(filePicker); }); @@ -206,26 +206,26 @@ }; vm.addStylesheetForBlock = function(block) { - localizationService.localize("blockEditor_headlineAddCustomStylesheet").then(localizedTitle => { + localizationService.localize("blockEditor_headlineAddCustomStylesheet").then(localizedTitle => { - const filePicker = { - title: localizedTitle, - isDialog: true, - filter: i => { - return !(i.name.indexOf(".css") !== -1); - }, - filterCssClass: "not-allowed", - select: node => { - const filepath = decodeURIComponent(node.id.replace(/\+/g, " ")); - block.stylesheet = "~/" + filepath; - editorService.close(); - }, - close: () => editorService.close() - }; + const filePicker = { + title: localizedTitle, + isDialog: true, + filter: i => { + return !(i.name.indexOf(".css") !== -1); + }, + filterCssClass: "not-allowed", + select: node => { + const filepath = decodeURIComponent(node.id.replace(/\+/g, " ")); + block.stylesheet = "~/" + filepath.replace("wwwroot/", ""); + editorService.close(); + }, + close: () => editorService.close() + }; - editorService.filePicker(filePicker); + editorService.staticFilePicker(filePicker); - }); + }); }; vm.requestRemoveStylesheetForBlock = function(block) { @@ -251,7 +251,7 @@ vm.addThumbnailForBlock = function(block) { localizationService.localize("blockEditor_headlineAddThumbnail").then(localizedTitle => { - + let allowedFileExtensions = ['jpg', 'jpeg', 'png', 'svg', 'webp', 'gif']; const thumbnailPicker = { @@ -269,7 +269,7 @@ }, close: () => editorService.close() }; - + editorService.staticFilePicker(thumbnailPicker); });