Fixes mandatory validation issue when creating a new document and a mandatory language has validation problems

This commit is contained in:
Shannon
2019-03-14 14:18:42 +11:00
parent 4eedc3ce5c
commit 9d8b5be650
4 changed files with 125 additions and 33 deletions

View File

@@ -12,6 +12,31 @@ namespace Umbraco.Tests.Web
public class ModelStateExtensionsTests
{
[Test]
public void Get_Cultures_With_Errors()
{
var ms = new ModelStateDictionary();
var localizationService = new Mock<ILocalizationService>();
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property
ms.AddPropertyError(new ValidationResult("title missing"), "title", "en-US"); //variant property
var result = ms.GetCulturesWithErrors(localizationService.Object);
//even though there are 2 errors, they are both for en-US since that is the default language and one of the errors is for an invariant property
Assert.AreEqual(1, result.Count);
Assert.AreEqual("en-US", result[0]);
ms = new ModelStateDictionary();
ms.AddCultureValidationError("en-US", "generic culture error");
result = ms.GetCulturesWithErrors(localizationService.Object);
Assert.AreEqual(1, result.Count);
Assert.AreEqual("en-US", result[0]);
}
[Test]
public void Get_Cultures_With_Property_Errors()
{

View File

@@ -1242,6 +1242,7 @@ To manage your website, simply open the Umbraco back office and start adding con
%0% can not be published, because a parent page is not published.
]]></key>
<key alias="contentPublishedFailedByMissingName"><![CDATA[%0% can not be published, because its missing a name.]]></key>
<key alias="contentPublishedFailedReqCultureValidationError">Validation failed for required language '%0%'. The language was saved but not published.</key>
<key alias="inProgress">Publishing in progress - please wait...</key>
<key alias="inProgressCounter">%0% out of %1% pages have been published...</key>
<key alias="nodePublish">%0% has been published</key>
@@ -1409,7 +1410,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="deleteUserSuccess">User %0% was deleted</key>
<key alias="resendInviteHeader">Invite user</key>
<key alias="resendInviteSuccess">Invitation has been re-sent to %0%</key>
<key alias="contentReqCulturePublishError">Cannot publish the document since the required '%0%' is not published</key>
<key alias="contentReqCulturePublishError">Cannot publish the document since the required '%0%' is not published</key>
<key alias="contentCultureValidationError">Validation failed for language '%0%'</key>
<key alias="documentTypeExportedSuccess">Document type was exported to file</key>
<key alias="documentTypeExportedError">An error occurred while exporting the document type</key>

View File

@@ -689,7 +689,7 @@ namespace Umbraco.Web.Editors
{
if (variantCount > 1)
{
var cultureErrors = ModelState.GetCulturesWithPropertyErrors(Services.LocalizationService);
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService);
foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray())
{
AddSuccessNotification(notifications, c,
@@ -882,7 +882,7 @@ namespace Umbraco.Web.Editors
{
if (variantCount > 1)
{
var cultureErrors = ModelState.GetCulturesWithPropertyErrors(Services.LocalizationService);
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService);
foreach (var c in contentItem.Variants.Where(x => x.Save && !cultureErrors.Contains(x.Culture)).Select(x => x.Culture).ToArray())
{
AddSuccessNotification(notifications, c,
@@ -1142,16 +1142,19 @@ namespace Umbraco.Web.Editors
var mandatoryCultures = _allLangs.Value.Values.Where(x => x.IsMandatory).Select(x => x.IsoCode).ToList();
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService);
//validate if we can publish based on the mandatory language requirements
var canPublish = ValidatePublishingMandatoryLanguages(
contentItem, cultureVariants, mandatoryCultures, "speechBubbles/contentReqCulturePublishError",
mandatoryVariant => mandatoryVariant.Publish, out var _);
cultureErrors,
contentItem, cultureVariants, mandatoryCultures,
mandatoryVariant => mandatoryVariant.Publish);
//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(Services.LocalizationService);
foreach (var variant in contentItem.Variants)
{
if (cultureErrors.Contains(variant.Culture))
@@ -1211,16 +1214,21 @@ namespace Umbraco.Web.Editors
var mandatoryCultures = _allLangs.Value.Values.Where(x => x.IsMandatory).Select(x => x.IsoCode).ToList();
//validate if we can publish based on the mandatory language requirements
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService);
//validate if we can publish based on the mandatory languages selected
var canPublish = ValidatePublishingMandatoryLanguages(
contentItem, cultureVariants, mandatoryCultures, "speechBubbles/contentReqCulturePublishError",
mandatoryVariant => mandatoryVariant.Publish, out var _);
cultureErrors,
contentItem, cultureVariants, mandatoryCultures,
mandatoryVariant => mandatoryVariant.Publish);
//if none are published and there are validation errors for mandatory cultures, then we can't publish anything
//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(Services.LocalizationService);
//It is a requirement that this is performed AFTER ValidatePublishingMandatoryLanguages.
foreach (var variant in contentItem.Variants)
{
if (cultureErrors.Contains(variant.Culture))
@@ -1260,23 +1268,21 @@ namespace Umbraco.Web.Editors
/// <summary>
/// Validate if publishing is possible based on the mandatory language requirements
/// </summary>
/// <param name="culturesWithValidationErrors"></param>
/// <param name="contentItem"></param>
/// <param name="cultureVariants"></param>
/// <param name="mandatoryCultures"></param>
/// <param name="localizationKey"></param>
/// <param name="publishingCheck"></param>
/// <param name="mandatoryVariants"></param>
/// <returns></returns>
private bool ValidatePublishingMandatoryLanguages(
IReadOnlyCollection<string> culturesWithValidationErrors,
ContentItemSave contentItem,
IReadOnlyCollection<ContentVariantSave> cultureVariants,
IReadOnlyList<string> mandatoryCultures,
string localizationKey,
Func<ContentVariantSave, bool> publishingCheck,
out IReadOnlyList<(ContentVariantSave mandatoryVariant, bool isPublished)> mandatoryVariants)
Func<ContentVariantSave, bool> publishingCheck)
{
var canPublish = true;
var result = new List<(ContentVariantSave, bool)>();
var result = new List<(ContentVariantSave model, bool publishing, bool isValid)>();
foreach (var culture in mandatoryCultures)
{
@@ -1285,18 +1291,39 @@ namespace Umbraco.Web.Editors
var mandatoryVariant = cultureVariants.First(x => x.Culture.InvariantEquals(culture));
var isPublished = contentItem.PersistedContent.Published && contentItem.PersistedContent.IsCulturePublished(culture);
result.Add((mandatoryVariant, isPublished));
var isPublishing = isPublished || publishingCheck(mandatoryVariant);
var isValid = !culturesWithValidationErrors.InvariantContains(culture);
if (isPublished || isPublishing) continue;
//cannot continue publishing since a required language that is not currently being published isn't published
AddCultureValidationError(culture, localizationKey);
canPublish = false;
result.Add((mandatoryVariant, isPublished || isPublishing, isValid));
}
//iterate over the results by invalid first
string firstInvalidMandatoryCulture = null;
foreach (var r in result.OrderBy(x => x.isValid))
{
if (!r.isValid)
firstInvalidMandatoryCulture = r.model.Culture;
if (r.publishing && !r.isValid)
{
//flagged for publishing but the mandatory culture is invalid
AddCultureValidationError(r.model.Culture, "publish/contentPublishedFailedReqCultureValidationError");
canPublish = false;
}
else if (r.publishing && r.isValid && firstInvalidMandatoryCulture != null)
{
//in this case this culture also cannot be published because another mandatory culture is invalid
AddCultureValidationError(r.model.Culture, "publish/contentPublishedFailedReqCultureValidationError", firstInvalidMandatoryCulture);
canPublish = false;
}
else if (!r.publishing)
{
//cannot continue publishing since a required culture that is not currently being published isn't published
AddCultureValidationError(r.model.Culture, "speechBubbles/contentReqCulturePublishError");
canPublish = false;
}
}
mandatoryVariants = result;
return canPublish;
}
@@ -1328,14 +1355,15 @@ namespace Umbraco.Web.Editors
/// <summary>
/// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs
/// </summary>
/// <param name="culture"></param>
/// <param name="culture">Culture to assign the error to</param>
/// <param name="localizationKey"></param>
private void AddCultureValidationError(string culture, string localizationKey)
/// <param name="cultureToken">
/// The culture used in the localization message, null by default which means <see cref="culture"/> will be used.
/// </param>
private void AddCultureValidationError(string culture, string localizationKey, string cultureToken = null)
{
var key = "_content_variant_" + culture + "_";
if (ModelState.ContainsKey(key)) return;
var errMsg = Services.TextService.Localize(localizationKey, new[] { _allLangs.Value[culture].CultureName });
ModelState.AddModelError(key, errMsg);
var errMsg = Services.TextService.Localize(localizationKey, new[] { cultureToken == null ? _allLangs.Value[culture].CultureName : _allLangs.Value[cultureToken].CultureName });
ModelState.AddCultureValidationError(culture, errMsg);
}
/// <summary>
@@ -1763,7 +1791,7 @@ namespace Umbraco.Web.Editors
if (!ModelState.IsValid && display.Variants.Count() > 1)
{
//Add any culture specific errors here
var cultureErrors = ModelState.GetCulturesWithPropertyErrors(Services.LocalizationService);
var cultureErrors = ModelState.GetCulturesWithErrors(Services.LocalizationService);
foreach (var cultureError in cultureErrors)
{

View File

@@ -58,7 +58,21 @@ namespace Umbraco.Web
}
/// <summary>
/// Returns a list of cultures that have property errors
/// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs
/// </summary>
/// <param name="modelState"></param>
/// <param name="culture"></param>
/// <param name="errMsg"></param>
internal static void AddCultureValidationError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState,
string culture, string errMsg)
{
var key = "_content_variant_" + culture + "_";
if (modelState.ContainsKey(key)) return;
modelState.AddModelError(key, errMsg);
}
/// <summary>
/// Returns a list of cultures that have property validation errors errors
/// </summary>
/// <param name="modelState"></param>
/// <param name="localizationService"></param>
@@ -81,6 +95,30 @@ namespace Umbraco.Web
return cultureErrors;
}
/// <summary>
/// Returns a list of cultures that have any validation errors
/// </summary>
/// <param name="modelState"></param>
/// <param name="localizationService"></param>
/// <returns></returns>
internal static IReadOnlyList<string> GetCulturesWithErrors(this System.Web.Http.ModelBinding.ModelStateDictionary modelState,
ILocalizationService localizationService)
{
var propertyCultureErrors = modelState.GetCulturesWithPropertyErrors(localizationService);
//now check the other special culture errors that are
var genericCultureErrors = modelState.Keys
.Where(x => x.StartsWith("_content_variant_") && x.EndsWith("_"))
.Select(x => x.TrimStart("_content_variant_").TrimEnd("_"))
.Where(x => !x.IsNullOrWhiteSpace())
//if it's marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language
//so errors for those must show up under the default lang.
.Select(x => x == "invariant" ? localizationService.GetDefaultLanguageIsoCode() : x)
.Distinct();
return propertyCultureErrors.Union(genericCultureErrors).ToList();
}
/// <summary>
/// Adds the error to model state correctly for a property so we can use it on the client side.
/// </summary>