From 8042405d942f2743ecbaa945dc7738e1afa89d8c Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Mar 2019 12:41:02 +1100 Subject: [PATCH] WIP - Fixing up validation so that invariant properties are only validated on the default lang or if the item is not published --- .../Models/ContentRepositoryExtensions.cs | 57 ++++++++++--------- src/Umbraco.Core/Models/CultureType.cs | 46 +++++++++++++++ .../Implement/LanguageRepositoryExtensions.cs | 10 ++++ .../Services/Implement/ContentService.cs | 33 +++++++---- .../Services/PropertyValidationService.cs | 40 +++++++++---- src/Umbraco.Core/Umbraco.Core.csproj | 2 + src/Umbraco.Tests/Models/ContentTests.cs | 8 +-- src/Umbraco.Tests/Models/VariationTests.cs | 32 ++++++----- .../NPocoTests/PetaPocoCachesTest.cs | 4 +- .../Repositories/DocumentRepositoryTest.cs | 14 ++--- .../Services/ContentServiceTagsTests.cs | 4 +- .../Services/ContentServiceTests.cs | 10 ++-- .../Services/PerformanceTests.cs | 4 +- .../content/umbnotificationlist.directive.js | 22 +++++++ .../content/umb-notification-list.html | 8 +++ .../src/views/content/overlays/publish.html | 6 +- .../content/overlays/publishdescendants.html | 6 +- .../src/views/content/overlays/save.html | 6 +- .../src/views/content/overlays/schedule.html | 5 +- .../views/content/overlays/sendtopublish.html | 5 +- src/Umbraco.Web/Editors/ContentController.cs | 32 +++++++---- 21 files changed, 240 insertions(+), 114 deletions(-) create mode 100644 src/Umbraco.Core/Models/CultureType.cs create mode 100644 src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepositoryExtensions.cs create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbnotificationlist.directive.js create mode 100644 src/Umbraco.Web.UI.Client/src/views/components/content/umb-notification-list.html diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs index c0e4cd7032..67629c076b 100644 --- a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs +++ b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs @@ -1,11 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Umbraco.Core.Collections; -using Umbraco.Core.Composing; using Umbraco.Core.Exceptions; -using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Services; namespace Umbraco.Core.Models { @@ -173,46 +169,55 @@ namespace Umbraco.Core.Models /// /// A value indicating whether it was possible to publish the names and values for the specified /// culture(s). The method may fail if required names are not set, but it does NOT validate property data - public static bool PublishCulture(this IContent content, string culture = "*") + public static bool PublishCulture(this IContent content, CultureType culture = null) { - culture = culture.NullOrWhiteSpaceAsNull(); + culture = culture ?? CultureType.All; // the variation should be supported by the content type properties // if the content type is invariant, only '*' and 'null' is ok // if the content type varies, everything is ok because some properties may be invariant - if (!content.ContentType.SupportsPropertyVariation(culture, "*", true)) + if (!content.ContentType.SupportsPropertyVariation(culture.Culture, "*", true)) throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\"."); var alsoInvariant = false; - if (culture == "*") // all cultures + + switch (culture.CultureBehavior) { - foreach (var c in content.AvailableCultures) + case CultureType.Behavior.All: { - var name = content.GetCultureName(c); + foreach (var c in content.AvailableCultures) + { + var name = content.GetCultureName(c); + if (string.IsNullOrWhiteSpace(name)) + return false; + content.SetPublishInfo(c, name, DateTime.Now); + } + break; + } + case CultureType.Behavior.Invariant: + { + if (string.IsNullOrWhiteSpace(content.Name)) + return false; + // PublishName set by repository - nothing to do here + break; + } + case CultureType.Behavior.Explicit: + { + var name = content.GetCultureName(culture.Culture); if (string.IsNullOrWhiteSpace(name)) return false; - content.SetPublishInfo(c, name, DateTime.Now); + content.SetPublishInfo(culture.Culture, name, DateTime.Now); + alsoInvariant = culture.IsDefaultCulture; // we also want to publish invariant values + break; } - } - else if (culture == null) // invariant culture - { - if (string.IsNullOrWhiteSpace(content.Name)) - return false; - // PublishName set by repository - nothing to do here - } - else // one single culture - { - var name = content.GetCultureName(culture); - if (string.IsNullOrWhiteSpace(name)) - return false; - content.SetPublishInfo(culture, name, DateTime.Now); - alsoInvariant = true; // we also want to publish invariant values + default: + throw new ArgumentOutOfRangeException(); } // property.PublishValues only publishes what is valid, variation-wise foreach (var property in content.Properties) { - property.PublishValues(culture); + property.PublishValues(culture.Culture); if (alsoInvariant) property.PublishValues(null); } diff --git a/src/Umbraco.Core/Models/CultureType.cs b/src/Umbraco.Core/Models/CultureType.cs new file mode 100644 index 0000000000..3b6b1de8cd --- /dev/null +++ b/src/Umbraco.Core/Models/CultureType.cs @@ -0,0 +1,46 @@ +namespace Umbraco.Core.Models +{ + /// + /// A represents either All cultures, a Single culture or the Invariant culture + /// + internal class CultureType + { + /// + /// Represents All cultures + /// + public static CultureType All { get; } = new CultureType("*"); + + /// + /// Represents the Invariant culture + /// + public static CultureType Invariant { get; } = new CultureType(null); + + /// + /// Represents a Single culture + /// + /// + /// + /// + public static CultureType Single(string culture, bool isDefault) + { + return new CultureType(culture, isDefault); + } + + private CultureType(string culture, bool isDefault = false) + { + Culture = culture; + IsDefaultCulture = isDefault; + } + + public string Culture { get; } + public Behavior CultureBehavior => Culture == "*" ? Behavior.All : Culture == null ? Behavior.Invariant : Behavior.Explicit; + public bool IsDefaultCulture { get; } + + public enum Behavior + { + All, + Invariant, + Explicit + } + } +} diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepositoryExtensions.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepositoryExtensions.cs new file mode 100644 index 0000000000..ce31c5faca --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepositoryExtensions.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Persistence.Repositories.Implement +{ + internal static class LanguageRepositoryExtensions + { + public static bool IsDefault(this ILanguageRepository repo, string culture) + { + return repo.GetDefaultIsoCode().InvariantEquals(culture); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index 85a32a30b8..916c51b4fe 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Repositories; +using Umbraco.Core.Persistence.Repositories.Implement; using Umbraco.Core.PropertyEditors; using Umbraco.Core.Scoping; using Umbraco.Core.Services.Changes; @@ -886,12 +887,13 @@ namespace Umbraco.Core.Services.Implement if (!culture.IsNullOrWhiteSpace() && culture != "*") { // publish the invariant values + // fixme: really? shouldn't we only publish invariant values if the culture is the default? var publishInvariant = content.PublishCulture(null); if (!publishInvariant) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); //validate the property values - if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties)) + if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, CultureType.Single(culture, _languageRepository.IsDefault(culture)))) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) { InvalidProperties = invalidProperties @@ -899,12 +901,12 @@ namespace Umbraco.Core.Services.Implement } // publish the culture(s) - var publishCulture = content.PublishCulture(culture); + var publishCulture = content.PublishCulture(CultureType.All); if (!publishCulture) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); //validate the property values - if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties)) + if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, CultureType.All)) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) { InvalidProperties = invalidProperties @@ -941,14 +943,17 @@ namespace Umbraco.Core.Services.Implement : new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); } + //fixme: Shouldn't we makes ure that all string cultures here are valid? i.e. no * or null is allowed when using this method - if (cultures.Select(content.PublishCulture).Any(isValid => !isValid)) + var cultureTypes = cultures.ToDictionary(x => x, x => CultureType.Single(x, _languageRepository.IsDefault(x))); + + if (cultureTypes.Select(x => content.PublishCulture(x.Value)).Any(isValid => !isValid)) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); //validate the property values on the cultures trying to be published - foreach (var culture in cultures) + foreach (var culture in cultureTypes) { - if (!_propertyValidationService.Value.IsPropertyDataValid(content, out var invalidProperties, culture)) + if (!_propertyValidationService.Value.IsPropertyDataValid(content, out var invalidProperties, culture.Value)) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content) { InvalidProperties = invalidProperties @@ -1333,7 +1338,8 @@ namespace Umbraco.Core.Services.Implement //publish the culture values and validate the property values, if validation fails, log the invalid properties so the develeper has an idea of what has failed Property[] invalidProperties = null; - var tryPublish = d.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties); + var cultureType = CultureType.Single(culture, _languageRepository.IsDefault(culture)); + var tryPublish = d.PublishCulture(cultureType) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, cultureType); if (invalidProperties != null && invalidProperties.Length > 0) Logger.Warn("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", d.Id, culture, string.Join(",", invalidProperties.Select(x => x.Alias))); @@ -1429,9 +1435,16 @@ namespace Umbraco.Core.Services.Implement // variant content type - publish specified cultures // invariant content type - publish only the invariant culture - return content.ContentType.VariesByCulture() - ? culturesToPublish.All(culture => content.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(content, out _)) - : content.PublishCulture() && _propertyValidationService.Value.IsPropertyDataValid(content, out _); + if (content.ContentType.VariesByCulture()) + { + return culturesToPublish.All(culture => + { + var cultureType = CultureType.Single(culture, _languageRepository.IsDefault(culture)); + return content.PublishCulture(cultureType) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, cultureType); + }); + } + + return content.PublishCulture(CultureType.Invariant) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, CultureType.Invariant); } // utility 'ShouldPublish' func used by SaveAndPublishBranch diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs index 146173dd5c..f05f40c953 100644 --- a/src/Umbraco.Core/Services/PropertyValidationService.cs +++ b/src/Umbraco.Core/Services/PropertyValidationService.cs @@ -31,9 +31,7 @@ namespace Umbraco.Core.Services /// /// Validates the content item's properties pass validation rules /// - /// If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor - /// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty. - public bool IsPropertyDataValid(IContentBase content, out Property[] invalidProperties, string culture = "*") + public bool IsPropertyDataValid(IContent content, out Property[] invalidProperties, CultureType culture) { // select invalid properties invalidProperties = content.Properties.Where(x => @@ -44,17 +42,35 @@ namespace Umbraco.Core.Services var varies = x.PropertyType.VariesByCulture(); - if (culture == null) - return !(varies || IsPropertyValid(x, null)); // validate invariant property, invariant culture + switch (culture.CultureBehavior) + { + case CultureType.Behavior.Invariant: + return !(varies || IsPropertyValid(x, null)); // validate invariant property, invariant culture + case CultureType.Behavior.All: + return !IsPropertyValid(x, culture.Culture); // validate property, all cultures + case CultureType.Behavior.Explicit: + if (varies) + { + return !IsPropertyValid(x, culture.Culture); // validate variant property, explicit culture + } + else + { + //We only want to validate the invariant property against an explicit culture if: + // * The culture is the default OR + // * The content item isn't published - if (culture == "*") - return !IsPropertyValid(x, culture); // validate property, all cultures + //This is because an invariant property is only edited on the default culture, but if the + //content item isn't published, we can't allow publishing of the specific non default culture + //if the invariant property data is invalid. - return varies - ? !IsPropertyValid(x, culture) // validate variant property, explicit culture - : !IsPropertyValid(x, null); // validate invariant property, explicit culture - }) - .ToArray(); + return (culture.IsDefaultCulture || !content.Published) + && !IsPropertyValid(x, null); // validate invariant property, explicit culture + } + default: + throw new ArgumentOutOfRangeException(); + } + + }).ToArray(); return invalidProperties.Length == 0; } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index fa046acd63..b8ab60eca6 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -209,7 +209,9 @@ + + diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs index 05f726893e..54934a339d 100644 --- a/src/Umbraco.Tests/Models/ContentTests.cs +++ b/src/Umbraco.Tests/Models/ContentTests.cs @@ -104,7 +104,7 @@ namespace Umbraco.Tests.Models Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - content.PublishCulture(langFr); //we've set the name, now we're publishing it + content.PublishCulture(CultureType.Single(langFr, false)); //we've set the name, now we're publishing it Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); //now it will be changed since the collection has changed var frCultureName = content.PublishCultureInfos[langFr]; Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); @@ -116,7 +116,7 @@ namespace Umbraco.Tests.Models Thread.Sleep(500); //The "Date" wont be dirty if the test runs too fast since it will be the same date content.SetCultureName("name-fr", langFr); - content.PublishCulture(langFr); //we've set the name, now we're publishing it + content.PublishCulture(CultureType.Single(langFr, false)); //we've set the name, now we're publishing it Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); //it's true now since we've updated a name } @@ -303,7 +303,7 @@ namespace Umbraco.Tests.Models content.SetCultureName("Hello", "en-US"); content.SetCultureName("World", "es-ES"); - content.PublishCulture("en-US"); + content.PublishCulture(CultureType.All); // should not try to clone something that's not Published or Unpublished // (and in fact it will not work) @@ -414,7 +414,7 @@ namespace Umbraco.Tests.Models content.SetCultureName("Hello", "en-US"); content.SetCultureName("World", "es-ES"); - content.PublishCulture("en-US"); + content.PublishCulture(CultureType.All); var i = 200; foreach (var property in content.Properties) diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs index 5569ecfa60..78de841445 100644 --- a/src/Umbraco.Tests/Models/VariationTests.cs +++ b/src/Umbraco.Tests/Models/VariationTests.cs @@ -275,7 +275,7 @@ namespace Umbraco.Tests.Models // can publish value // and get edited and published values - Assert.IsTrue(content.PublishCulture()); + Assert.IsTrue(content.PublishCulture(CultureType.Invariant)); Assert.AreEqual("a", content.GetValue("prop")); Assert.AreEqual("a", content.GetValue("prop", published: true)); @@ -305,9 +305,9 @@ namespace Umbraco.Tests.Models // can publish value // and get edited and published values - Assert.IsFalse(content.PublishCulture(langFr)); // no name + Assert.IsFalse(content.PublishCulture(CultureType.Single(langFr, false))); // no name content.SetCultureName("name-fr", langFr); - Assert.IsTrue(content.PublishCulture(langFr)); + Assert.IsTrue(content.PublishCulture(CultureType.Single(langFr, false))); Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); Assert.AreEqual("c", content.GetValue("prop", langFr)); @@ -321,7 +321,7 @@ namespace Umbraco.Tests.Models Assert.IsNull(content.GetValue("prop", langFr, published: true)); // can publish all - Assert.IsTrue(content.PublishCulture("*")); + Assert.IsTrue(content.PublishCulture(CultureType.All)); Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); Assert.AreEqual("c", content.GetValue("prop", langFr)); @@ -331,14 +331,14 @@ namespace Umbraco.Tests.Models content.UnpublishCulture(langFr); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.IsNull(content.GetValue("prop", langFr, published: true)); - content.PublishCulture(langFr); + content.PublishCulture(CultureType.Single(langFr, false)); Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.AreEqual("c", content.GetValue("prop", langFr, published: true)); content.UnpublishCulture(); // clears invariant props if any Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); - content.PublishCulture(); // publishes invariant props if any + content.PublishCulture(CultureType.Invariant); // publishes invariant props if any Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop", published: true)); @@ -384,15 +384,19 @@ namespace Umbraco.Tests.Models content.SetCultureName("hello", langFr); - Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here) - Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop1 is mandatory + var langFrCultureType = CultureType.Single(langFr, false); + + Assert.IsTrue(content.PublishCulture(CultureType.Single(langFr, false))); // succeeds because names are ok (not validating properties here) + Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFrCultureType));// fails because prop1 is mandatory content.SetValue("prop1", "a", langFr); - Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here) - Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop2 is mandatory and invariant + Assert.IsTrue(content.PublishCulture(CultureType.Single(langFr, false))); // succeeds because names are ok (not validating properties here) + // fails because prop2 is mandatory and invariant and the item isn't published. + // Invariant is validated against the default language except when there isn't a published version, in that case it's always validated. + Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFrCultureType)); content.SetValue("prop2", "x"); - Assert.IsTrue(content.PublishCulture(langFr)); // still ok... - Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// now it's ok + Assert.IsTrue(content.PublishCulture(CultureType.Single(langFr, false))); // still ok... + Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFrCultureType));// now it's ok Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true)); Assert.AreEqual("x", content.GetValue("prop2", published: true)); @@ -423,12 +427,12 @@ namespace Umbraco.Tests.Models content.SetValue("prop", "a-es", langEs); // cannot publish without a name - Assert.IsFalse(content.PublishCulture(langFr)); + Assert.IsFalse(content.PublishCulture(CultureType.Single(langFr, false))); // works with a name // and then FR is available, and published content.SetCultureName("name-fr", langFr); - Assert.IsTrue(content.PublishCulture(langFr)); + Assert.IsTrue(content.PublishCulture(CultureType.Single(langFr, false))); // now UK is available too content.SetCultureName("name-uk", langUk); diff --git a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs index f558a64499..6a7f929cb3 100644 --- a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs +++ b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs @@ -181,13 +181,13 @@ namespace Umbraco.Tests.Persistence.NPocoTests contentTypeService.Save(contentType); var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); content1.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content1.PublishCulture(); + content1.PublishCulture(CultureType.Invariant); contentService.SaveAndPublish(content1); id2 = content1.Id; var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); - content2.PublishCulture(); + content2.PublishCulture(CultureType.Invariant); contentService.SaveAndPublish(content2); id3 = content2.Id; diff --git a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs index 2c4f3f1908..32839da996 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs @@ -141,8 +141,8 @@ namespace Umbraco.Tests.Persistence.Repositories // publish = new edit version content1.SetValue("title", "title"); - ((Content)content1).PublishCulture(); - ((Content)content1).PublishedState = PublishedState.Publishing; + content1.PublishCulture(CultureType.Invariant); + content1.PublishedState = PublishedState.Publishing; repository.Save(content1); versions.Add(content1.VersionId); // NEW VERSION @@ -203,8 +203,8 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(false, scope.Database.ExecuteScalar($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); // publish = version - ((Content)content1).PublishCulture(); - ((Content)content1).PublishedState = PublishedState.Publishing; + content1.PublishCulture(CultureType.Invariant); + content1.PublishedState = PublishedState.Publishing; repository.Save(content1); versions.Add(content1.VersionId); // NEW VERSION @@ -239,8 +239,8 @@ namespace Umbraco.Tests.Persistence.Repositories // publish = new version content1.Name = "name-4"; content1.SetValue("title", "title-4"); - ((Content)content1).PublishCulture(); - ((Content)content1).PublishedState = PublishedState.Publishing; + content1.PublishCulture(CultureType.Invariant); + content1.PublishedState = PublishedState.Publishing; repository.Save(content1); versions.Add(content1.VersionId); // NEW VERSION @@ -654,7 +654,7 @@ namespace Umbraco.Tests.Persistence.Repositories // publish them all foreach (var content in result) { - content.PublishCulture(); + content.PublishCulture(CultureType.Invariant); repository.Save(content); } diff --git a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs index 5e97bea2c1..274a69e53b 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs @@ -517,7 +517,7 @@ namespace Umbraco.Tests.Services allTags = tagService.GetAllContentTags(); Assert.AreEqual(0, allTags.Count()); - content1.PublishCulture(); + content1.PublishCulture(CultureType.Invariant); contentService.SaveAndPublish(content1); Assert.IsTrue(content1.Published); @@ -601,7 +601,7 @@ namespace Umbraco.Tests.Services var allTags = tagService.GetAllContentTags(); Assert.AreEqual(0, allTags.Count()); - content1.PublishCulture(); + content1.PublishCulture(CultureType.Invariant); contentService.SaveAndPublish(content1); tags = tagService.GetTagsForEntity(content2.Id); diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 04cdc2aab7..8a43f2de4a 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -732,8 +732,8 @@ namespace Umbraco.Tests.Services IContent content = new Content("content", Constants.System.Root, contentType); content.SetCultureName("content-fr", langFr.IsoCode); content.SetCultureName("content-en", langUk.IsoCode); - content.PublishCulture(langFr.IsoCode); - content.PublishCulture(langUk.IsoCode); + content.PublishCulture(CultureType.Single(langFr.IsoCode, langFr.IsDefault)); + content.PublishCulture(CultureType.Single(langUk.IsoCode, langUk.IsDefault)); Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); @@ -988,7 +988,7 @@ namespace Umbraco.Tests.Services // content cannot publish values because they are invalid var propertyValidationService = new PropertyValidationService(Factory.GetInstance(), ServiceContext.DataTypeService); - var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties); + var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties, CultureType.Invariant); Assert.IsFalse(isValid); Assert.IsNotEmpty(invalidProperties); @@ -1018,7 +1018,7 @@ namespace Umbraco.Tests.Services content.SetCultureName("name-fr", langFr.IsoCode); content.SetCultureName("name-da", langDa.IsoCode); - content.PublishCulture(langFr.IsoCode); + content.PublishCulture(CultureType.Single(langFr.IsoCode, langFr.IsDefault)); var result = ((ContentService)ServiceContext.ContentService).CommitDocumentChanges(content); Assert.IsTrue(result.Success); content = ServiceContext.ContentService.GetById(content.Id); @@ -1026,7 +1026,7 @@ namespace Umbraco.Tests.Services Assert.IsFalse(content.IsCulturePublished(langDa.IsoCode)); content.UnpublishCulture(langFr.IsoCode); - content.PublishCulture(langDa.IsoCode); + content.PublishCulture(CultureType.Single(langDa.IsoCode, langDa.IsDefault)); result = ((ContentService)ServiceContext.ContentService).CommitDocumentChanges(content); Assert.IsTrue(result.Success); diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index 449e933c24..ba4c708c22 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -216,7 +216,7 @@ namespace Umbraco.Tests.Services var result = new List(); ServiceContext.ContentTypeService.Save(contentType1); IContent lastParent = MockedContent.CreateSimpleContent(contentType1); - lastParent.PublishCulture(); + lastParent.PublishCulture(CultureType.Invariant); ServiceContext.ContentService.SaveAndPublish(lastParent); result.Add(lastParent); //create 20 deep @@ -230,7 +230,7 @@ namespace Umbraco.Tests.Services //only publish evens if (j % 2 == 0) { - content.PublishCulture(); + content.PublishCulture(CultureType.Invariant); ServiceContext.ContentService.SaveAndPublish(content); } else diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbnotificationlist.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbnotificationlist.directive.js new file mode 100644 index 0000000000..9b6682cb35 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbnotificationlist.directive.js @@ -0,0 +1,22 @@ +(function () { + 'use strict'; + + function umbNotificationList() { + + var vm = this; + + } + + var umbNotificationListComponent = { + templateUrl: 'views/components/content/umb-notification-list.html', + bindings: { + notifications: "<" + }, + controllerAs: 'vm', + controller: umbNotificationList + }; + + angular.module("umbraco.directives") + .component('umbNotificationList', umbNotificationListComponent); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-notification-list.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-notification-list.html new file mode 100644 index 0000000000..739aab0730 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-notification-list.html @@ -0,0 +1,8 @@ + + + + + {{notification.message}} + + diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html index 1bb58c3cd2..3a8fd2b200 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html @@ -36,10 +36,8 @@ {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} - - - {{notification.message}} - + + diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html index e851029024..0045198134 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html @@ -64,10 +64,8 @@ {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} - - - {{notification.message}} - + + diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html index 1656e1761f..7df5b044db 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/save.html @@ -41,10 +41,8 @@ {{saveVariantSelectorForm.saveVariantSelector.errorMsg}} - - - {{notification.message}} - + + diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html index 930e001842..88ef5a206a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/schedule.html @@ -170,10 +170,7 @@
{{scheduleSelectorForm.saveVariantReleaseDate.errorMsg}}
-
- -
{{notification.message}}
-
+ diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html index c0bbb7fc0e..d10b8fab20 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/sendtopublish.html @@ -34,9 +34,8 @@ {{publishVariantSelectorForm.publishVariantSelector.errorMsg}} - - {{notification.message}} - + + diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b6d0e2a9ff..bf6c39cf9e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -728,7 +728,7 @@ namespace Umbraco.Web.Editors break; } - var publishStatus = PublishBranchInternal(contentItem, false, out wasCancelled, out var successfulCultures); + var publishStatus = PublishBranchInternal(contentItem, false, out wasCancelled, out var successfulCultures).ToList(); //global notifications AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); @@ -749,7 +749,7 @@ namespace Umbraco.Web.Editors break; } - var publishStatus = PublishBranchInternal(contentItem, true, out wasCancelled, out var successfulCultures); + var publishStatus = PublishBranchInternal(contentItem, true, out wasCancelled, out var successfulCultures).ToList(); //global notifications AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); @@ -1341,7 +1341,7 @@ namespace Umbraco.Web.Editors foreach (var variant in cultureVariants.Where(x => x.Publish)) { // publishing any culture, implies the invariant culture - var valid = persistentContent.PublishCulture(variant.Culture); + var valid = persistentContent.PublishCulture(CultureType.Single(variant.Culture, IsDefaultCulture(variant.Culture))); if (!valid) { AddCultureValidationError(variant.Culture, "speechBubbles/contentCultureValidationError"); @@ -1942,12 +1942,12 @@ namespace Umbraco.Web.Editors /// /// Adds notification messages to the outbound display model for a given published status /// - /// + /// /// /// /// This is null when dealing with invariant content, else it's the cultures that were successfully published /// - private void AddMessageForPublishStatus(IEnumerable statuses, INotificationModel display, string[] successfulCultures = null) + private void AddMessageForPublishStatus(IReadOnlyCollection statuses, INotificationModel display, string[] successfulCultures = null) { var totalStatusCount = statuses.Count(); @@ -2046,7 +2046,7 @@ namespace Umbraco.Web.Editors break; case PublishResultType.FailedPublishPathNotPublished: { - var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})")); + var names = string.Join(", ", status.Select(x => x.Content.Name)); display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedByParent", @@ -2055,13 +2055,13 @@ namespace Umbraco.Web.Editors break; case PublishResultType.FailedPublishCancelledByEvent: { - var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})")); + var names = string.Join(", ", status.Select(x => x.Content.Name)); AddCancelMessage(display, message: "publish/contentPublishedFailedByEvent", messageParams: new[] { names }); } break; case PublishResultType.FailedPublishAwaitingRelease: { - var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})")); + var names = string.Join(", ", status.Select(x => x.Content.Name)); display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", @@ -2070,7 +2070,7 @@ namespace Umbraco.Web.Editors break; case PublishResultType.FailedPublishHasExpired: { - var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})")); + var names = string.Join(", ", status.Select(x => x.Content.Name)); display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedExpired", @@ -2079,7 +2079,7 @@ namespace Umbraco.Web.Editors break; case PublishResultType.FailedPublishIsTrashed: { - var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})")); + var names = string.Join(", ", status.Select(x => x.Content.Name)); display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedIsTrashed", @@ -2088,7 +2088,7 @@ namespace Umbraco.Web.Editors break; case PublishResultType.FailedPublishContentInvalid: { - var names = string.Join(", ", status.Select(x => $"{x.Content.Name} ({x.Content.Id})")); + var names = string.Join(", ", status.Select(x => x.Content.Name)); display.AddWarningNotification( Services.TextService.Localize("publish"), Services.TextService.Localize("publish/contentPublishedFailedInvalid", @@ -2106,6 +2106,16 @@ namespace Umbraco.Web.Editors } } + /// + /// Returns true if the culture specified is the default culture + /// + /// + /// + private bool IsDefaultCulture(string culture) + { + return _allLangs.Value.Any(x => x.Value.IsDefault && x.Key.InvariantEquals(culture)); + } + /// /// Used to map an instance to a and ensuring a language is present if required ///