diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 8c3cc5f557..df80879037 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -587,14 +587,14 @@ namespace Umbraco.Web.Editors //this a custom check for any variants not being flagged for Saving since we'll need to manually //remove the ModelState validation for the Name - var variantIndex = 0; + var variantCount = 0; foreach (var variant in contentItem.Variants) { if (!variant.Save) { - ModelState.Remove($"Variants[{variantIndex}].Name"); + ModelState.Remove($"Variants[{variantCount}].Name"); } - variantIndex++; + variantCount++; } //We need to manually check the validation results here because: @@ -621,16 +621,20 @@ namespace Umbraco.Web.Editors } } - //if the model state is not valid we cannot publish so change it to save - switch (contentItem.Action) + //if there's only one variant and the model state is not valid we cannot publish so change it to save + if (variantCount == 1) { - case ContentSaveAction.Publish: - contentItem.Action = ContentSaveAction.Save; - break; - case ContentSaveAction.PublishNew: - contentItem.Action = ContentSaveAction.SaveNew; - break; + switch (contentItem.Action) + { + case ContentSaveAction.Publish: + contentItem.Action = ContentSaveAction.Save; + break; + case ContentSaveAction.PublishNew: + contentItem.Action = ContentSaveAction.SaveNew; + break; + } } + } //initialize this to successful @@ -729,33 +733,26 @@ namespace Umbraco.Web.Editors } else { - var canPublish = true; - //All variants in this collection should have a culture if we get here! but we'll double check and filter here var cultureVariants = contentItem.Variants.Where(x => !x.Culture.IsNullOrWhiteSpace()).ToList(); //check if we are publishing other variants and validate them var allLangs = Services.LocalizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase); - - //validate any mandatory variants that are not in the list - var mandatoryLangs = Mapper.Map, IEnumerable>(allLangs.Values).Where(x => x.Mandatory); - foreach (var lang in mandatoryLangs) + //validate if we can publish based on the mandatory language requirements + var canPublish = ValidatePublishingMandatoryLanguages(contentItem, allLangs, cultureVariants); + + //Now check if there are validation errors on each variant. + //If validation errors are detected on a variant and it's state is set to 'publish', then we + //need to change it to 'save'. + //It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages. + var cultureErrors = ModelState.GetCulturesWithPropertyErrors(); + foreach (var variant in contentItem.Variants) { - //Check if a mandatory language is missing from being published - - var variant = cultureVariants.First(x => x.Culture == lang.IsoCode); - var isPublished = contentItem.PersistedContent.IsCulturePublished(lang.IsoCode); - var isPublishing = variant.Publish; - - if (!isPublished && !isPublishing) - { - //cannot continue publishing since a required language that is not currently being published isn't published - AddCultureValidationError(lang.IsoCode, allLangs, "speechBubbles/contentReqCulturePublishError"); - canPublish = false; - } + if (cultureErrors.Contains(variant.Culture)) + variant.Publish = false; } - + if (canPublish) { //try to publish all the values on the model @@ -778,6 +775,38 @@ namespace Umbraco.Web.Editors } } + /// + /// Validate if publishing is possible based on the mandatory language requirements + /// + /// + /// + /// + /// + private bool ValidatePublishingMandatoryLanguages(ContentItemSave contentItem, IDictionary allLangs, IReadOnlyCollection cultureVariants) + { + var canPublish = true; + + //validate any mandatory variants that are not in the list + var mandatoryLangs = Mapper.Map, IEnumerable>(allLangs.Values).Where(x => x.Mandatory); + + foreach (var lang in mandatoryLangs) + { + //Check if a mandatory language is missing from being published + + var variant = cultureVariants.First(x => x.Culture == lang.IsoCode); + var isPublished = contentItem.PersistedContent.IsCulturePublished(lang.IsoCode); + var isPublishing = variant.Publish; + + if (isPublished || isPublishing) continue; + + //cannot continue publishing since a required language that is not currently being published isn't published + AddCultureValidationError(lang.IsoCode, allLangs, "speechBubbles/contentReqCulturePublishError"); + canPublish = false; + } + + return canPublish; + } + /// /// This will call PublishCulture on the content item for each culture that needs to be published including the invariant culture /// @@ -1194,13 +1223,7 @@ namespace Umbraco.Web.Editors if (!ModelState.IsValid) { //Add any culture specific errors here - var cultureErrors = ModelState.Keys - .Select(x => x.Split('.')) //split into parts - .Where(x => x.Length >= 3 && x[0] == "_Properties") //only choose _Properties errors - .Select(x => x[2]) //select the culture part - .Where(x => !x.IsNullOrWhiteSpace()) //if it has a value - .Distinct() - .ToList(); + var cultureErrors = ModelState.GetCulturesWithPropertyErrors(); var allLangs = Services.LocalizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase); diff --git a/src/Umbraco.Web/ModelStateExtensions.cs b/src/Umbraco.Web/ModelStateExtensions.cs index 012728ef5a..48eb06c88a 100644 --- a/src/Umbraco.Web/ModelStateExtensions.cs +++ b/src/Umbraco.Web/ModelStateExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web.Mvc; +using Umbraco.Core; namespace Umbraco.Web { @@ -37,20 +38,7 @@ namespace Umbraco.Web { return state.Where(v => v.Key.StartsWith(prefix + ".")).All(v => !v.Value.Errors.Any()); } - - - //NOTE: we used this alot in v5 when we had editors in MVC, this was really handy for knockout editors using JS - - ///// - ///// Adds an error to the model state that has to do with data validation, this is generally used for JSON responses - ///// - ///// - ///// - //public static void AddDataValidationError(this ModelStateDictionary state, string errorMessage) - //{ - // state.AddModelError("DataValidation", errorMessage); - //} - + /// /// Adds the error to model state correctly for a property so we can use it on the client side. /// @@ -66,6 +54,25 @@ namespace Umbraco.Web modelState.AddValidationError(result, "_Properties", propertyAlias, culture); } + /// + /// Returns a list of cultures that have property errors + /// + /// + /// + internal static IReadOnlyList GetCulturesWithPropertyErrors(this System.Web.Http.ModelBinding.ModelStateDictionary modelState) + { + //Add any culture specific errors here + var cultureErrors = modelState.Keys + .Select(x => x.Split('.')) //split into parts + .Where(x => x.Length >= 3 && x[0] == "_Properties") //only choose _Properties errors + .Select(x => x[2]) //select the culture part + .Where(x => !x.IsNullOrWhiteSpace()) //if it has a value + .Distinct() + .ToList(); + + return cultureErrors; + } + /// /// Adds the error to model state correctly for a property so we can use it on the client side. ///