WIP - Fixing up validation so that invariant properties are only validated on the default lang or if the item is not published

This commit is contained in:
Shannon
2019-03-27 12:41:02 +11:00
parent e222bf2920
commit 8042405d94
21 changed files with 240 additions and 114 deletions

View File

@@ -1,11 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Umbraco.Core.Collections;
using Umbraco.Core.Composing;
using Umbraco.Core.Exceptions; using Umbraco.Core.Exceptions;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
namespace Umbraco.Core.Models namespace Umbraco.Core.Models
{ {
@@ -173,18 +169,21 @@ namespace Umbraco.Core.Models
/// <param name="culture"></param> /// <param name="culture"></param>
/// <returns>A value indicating whether it was possible to publish the names and values for the specified /// <returns>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</returns> /// culture(s). The method may fail if required names are not set, but it does NOT validate property data</returns>
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 // 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 is invariant, only '*' and 'null' is ok
// if the content type varies, everything is ok because some properties may be invariant // 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}\"."); throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
var alsoInvariant = false; var alsoInvariant = false;
if (culture == "*") // all cultures
switch (culture.CultureBehavior)
{
case CultureType.Behavior.All:
{ {
foreach (var c in content.AvailableCultures) foreach (var c in content.AvailableCultures)
{ {
@@ -193,26 +192,32 @@ namespace Umbraco.Core.Models
return false; return false;
content.SetPublishInfo(c, name, DateTime.Now); content.SetPublishInfo(c, name, DateTime.Now);
} }
break;
} }
else if (culture == null) // invariant culture case CultureType.Behavior.Invariant:
{ {
if (string.IsNullOrWhiteSpace(content.Name)) if (string.IsNullOrWhiteSpace(content.Name))
return false; return false;
// PublishName set by repository - nothing to do here // PublishName set by repository - nothing to do here
break;
} }
else // one single culture case CultureType.Behavior.Explicit:
{ {
var name = content.GetCultureName(culture); var name = content.GetCultureName(culture.Culture);
if (string.IsNullOrWhiteSpace(name)) if (string.IsNullOrWhiteSpace(name))
return false; return false;
content.SetPublishInfo(culture, name, DateTime.Now); content.SetPublishInfo(culture.Culture, name, DateTime.Now);
alsoInvariant = true; // we also want to publish invariant values alsoInvariant = culture.IsDefaultCulture; // we also want to publish invariant values
break;
}
default:
throw new ArgumentOutOfRangeException();
} }
// property.PublishValues only publishes what is valid, variation-wise // property.PublishValues only publishes what is valid, variation-wise
foreach (var property in content.Properties) foreach (var property in content.Properties)
{ {
property.PublishValues(culture); property.PublishValues(culture.Culture);
if (alsoInvariant) if (alsoInvariant)
property.PublishValues(null); property.PublishValues(null);
} }

View File

@@ -0,0 +1,46 @@
namespace Umbraco.Core.Models
{
/// <summary>
/// A <see cref="CultureType"/> represents either All cultures, a Single culture or the Invariant culture
/// </summary>
internal class CultureType
{
/// <summary>
/// Represents All cultures
/// </summary>
public static CultureType All { get; } = new CultureType("*");
/// <summary>
/// Represents the Invariant culture
/// </summary>
public static CultureType Invariant { get; } = new CultureType(null);
/// <summary>
/// Represents a Single culture
/// </summary>
/// <param name="culture"></param>
/// <param name="isDefault"></param>
/// <returns></returns>
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
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -10,6 +10,7 @@ using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories; using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Scoping; using Umbraco.Core.Scoping;
using Umbraco.Core.Services.Changes; using Umbraco.Core.Services.Changes;
@@ -886,12 +887,13 @@ namespace Umbraco.Core.Services.Implement
if (!culture.IsNullOrWhiteSpace() && culture != "*") if (!culture.IsNullOrWhiteSpace() && culture != "*")
{ {
// publish the invariant values // publish the invariant values
// fixme: really? shouldn't we only publish invariant values if the culture is the default?
var publishInvariant = content.PublishCulture(null); var publishInvariant = content.PublishCulture(null);
if (!publishInvariant) if (!publishInvariant)
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
//validate the property values //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) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
{ {
InvalidProperties = invalidProperties InvalidProperties = invalidProperties
@@ -899,12 +901,12 @@ namespace Umbraco.Core.Services.Implement
} }
// publish the culture(s) // publish the culture(s)
var publishCulture = content.PublishCulture(culture); var publishCulture = content.PublishCulture(CultureType.All);
if (!publishCulture) if (!publishCulture)
return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content); return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
//validate the property values //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) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
{ {
InvalidProperties = invalidProperties InvalidProperties = invalidProperties
@@ -941,14 +943,17 @@ namespace Umbraco.Core.Services.Implement
: new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content); : 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); return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
//validate the property values on the cultures trying to be published //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) return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
{ {
InvalidProperties = invalidProperties 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 //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; 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) if (invalidProperties != null && invalidProperties.Length > 0)
Logger.Warn<ContentService>("Scheduled publishing will fail for document {DocumentId} and culture {Culture} because of invalid properties {InvalidProperties}", Logger.Warn<ContentService>("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))); 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 // variant content type - publish specified cultures
// invariant content type - publish only the invariant culture // invariant content type - publish only the invariant culture
return content.ContentType.VariesByCulture() if (content.ContentType.VariesByCulture())
? culturesToPublish.All(culture => content.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(content, out _)) {
: content.PublishCulture() && _propertyValidationService.Value.IsPropertyDataValid(content, out _); 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 // utility 'ShouldPublish' func used by SaveAndPublishBranch

View File

@@ -31,9 +31,7 @@ namespace Umbraco.Core.Services
/// <summary> /// <summary>
/// Validates the content item's properties pass validation rules /// Validates the content item's properties pass validation rules
/// </summary> /// </summary>
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor public bool IsPropertyDataValid(IContent content, out Property[] invalidProperties, CultureType culture)
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>
public bool IsPropertyDataValid(IContentBase content, out Property[] invalidProperties, string culture = "*")
{ {
// select invalid properties // select invalid properties
invalidProperties = content.Properties.Where(x => invalidProperties = content.Properties.Where(x =>
@@ -44,17 +42,35 @@ namespace Umbraco.Core.Services
var varies = x.PropertyType.VariesByCulture(); var varies = x.PropertyType.VariesByCulture();
if (culture == null) switch (culture.CultureBehavior)
{
case CultureType.Behavior.Invariant:
return !(varies || IsPropertyValid(x, null)); // validate invariant property, invariant culture 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 == "*") //This is because an invariant property is only edited on the default culture, but if the
return !IsPropertyValid(x, culture); // validate property, all cultures //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 return (culture.IsDefaultCulture || !content.Published)
? !IsPropertyValid(x, culture) // validate variant property, explicit culture && !IsPropertyValid(x, null); // validate invariant property, explicit culture
: !IsPropertyValid(x, null); // validate invariant property, explicit culture }
}) default:
.ToArray(); throw new ArgumentOutOfRangeException();
}
}).ToArray();
return invalidProperties.Length == 0; return invalidProperties.Length == 0;
} }

View File

@@ -209,7 +209,9 @@
<Compile Include="IO\MediaPathSchemes\UniqueMediaPathScheme.cs" /> <Compile Include="IO\MediaPathSchemes\UniqueMediaPathScheme.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" /> <Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_1\ChangeNuCacheJsonFormat.cs" /> <Compile Include="Migrations\Upgrade\V_8_0_1\ChangeNuCacheJsonFormat.cs" />
<Compile Include="Models\CultureType.cs" />
<Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" /> <Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" />
<Compile Include="Persistence\Repositories\Implement\LanguageRepositoryExtensions.cs" />
<Compile Include="PropertyEditors\DateTimeConfiguration.cs" /> <Compile Include="PropertyEditors\DateTimeConfiguration.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\RenameLabelAndRichTextPropertyEditorAliases.cs" /> <Compile Include="Migrations\Upgrade\V_8_0_0\RenameLabelAndRichTextPropertyEditorAliases.cs" />
<Compile Include="PublishedModelFactoryExtensions.cs" /> <Compile Include="PublishedModelFactoryExtensions.cs" />

View File

@@ -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 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.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 Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); //now it will be changed since the collection has changed
var frCultureName = content.PublishCultureInfos[langFr]; var frCultureName = content.PublishCultureInfos[langFr];
Assert.IsTrue(frCultureName.IsPropertyDirty("Date")); 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 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.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(frCultureName.IsPropertyDirty("Date"));
Assert.IsTrue(content.IsPropertyDirty("PublishCultureInfos")); //it's true now since we've updated a name 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("Hello", "en-US");
content.SetCultureName("World", "es-ES"); 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 // should not try to clone something that's not Published or Unpublished
// (and in fact it will not work) // (and in fact it will not work)
@@ -414,7 +414,7 @@ namespace Umbraco.Tests.Models
content.SetCultureName("Hello", "en-US"); content.SetCultureName("Hello", "en-US");
content.SetCultureName("World", "es-ES"); content.SetCultureName("World", "es-ES");
content.PublishCulture("en-US"); content.PublishCulture(CultureType.All);
var i = 200; var i = 200;
foreach (var property in content.Properties) foreach (var property in content.Properties)

View File

@@ -275,7 +275,7 @@ namespace Umbraco.Tests.Models
// can publish value // can publish value
// and get edited and published values // 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"));
Assert.AreEqual("a", content.GetValue("prop", published: true)); Assert.AreEqual("a", content.GetValue("prop", published: true));
@@ -305,9 +305,9 @@ namespace Umbraco.Tests.Models
// can publish value // can publish value
// and get edited and published values // 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); 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"));
Assert.IsNull(content.GetValue("prop", published: true)); Assert.IsNull(content.GetValue("prop", published: true));
Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.AreEqual("c", content.GetValue("prop", langFr));
@@ -321,7 +321,7 @@ namespace Umbraco.Tests.Models
Assert.IsNull(content.GetValue("prop", langFr, published: true)); Assert.IsNull(content.GetValue("prop", langFr, published: true));
// can publish all // can publish all
Assert.IsTrue(content.PublishCulture("*")); Assert.IsTrue(content.PublishCulture(CultureType.All));
Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop"));
Assert.IsNull(content.GetValue("prop", published: true)); Assert.IsNull(content.GetValue("prop", published: true));
Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.AreEqual("c", content.GetValue("prop", langFr));
@@ -331,14 +331,14 @@ namespace Umbraco.Tests.Models
content.UnpublishCulture(langFr); content.UnpublishCulture(langFr);
Assert.AreEqual("c", content.GetValue("prop", langFr)); Assert.AreEqual("c", content.GetValue("prop", langFr));
Assert.IsNull(content.GetValue("prop", langFr, published: true)); 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));
Assert.AreEqual("c", content.GetValue("prop", langFr, published: true)); Assert.AreEqual("c", content.GetValue("prop", langFr, published: true));
content.UnpublishCulture(); // clears invariant props if any content.UnpublishCulture(); // clears invariant props if any
Assert.IsNull(content.GetValue("prop")); Assert.IsNull(content.GetValue("prop"));
Assert.IsNull(content.GetValue("prop", published: true)); 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"));
Assert.IsNull(content.GetValue("prop", published: true)); Assert.IsNull(content.GetValue("prop", published: true));
@@ -384,15 +384,19 @@ namespace Umbraco.Tests.Models
content.SetCultureName("hello", langFr); content.SetCultureName("hello", langFr);
Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here) var langFrCultureType = CultureType.Single(langFr, false);
Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop1 is mandatory
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); content.SetValue("prop1", "a", langFr);
Assert.IsTrue(content.PublishCulture(langFr)); // succeeds because names are ok (not validating properties here) Assert.IsTrue(content.PublishCulture(CultureType.Single(langFr, false))); // succeeds because names are ok (not validating properties here)
Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// fails because prop2 is mandatory and invariant // 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"); content.SetValue("prop2", "x");
Assert.IsTrue(content.PublishCulture(langFr)); // still ok... Assert.IsTrue(content.PublishCulture(CultureType.Single(langFr, false))); // still ok...
Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFr));// now it's ok Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFrCultureType));// now it's ok
Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true)); Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true));
Assert.AreEqual("x", content.GetValue("prop2", published: true)); Assert.AreEqual("x", content.GetValue("prop2", published: true));
@@ -423,12 +427,12 @@ namespace Umbraco.Tests.Models
content.SetValue("prop", "a-es", langEs); content.SetValue("prop", "a-es", langEs);
// cannot publish without a name // cannot publish without a name
Assert.IsFalse(content.PublishCulture(langFr)); Assert.IsFalse(content.PublishCulture(CultureType.Single(langFr, false)));
// works with a name // works with a name
// and then FR is available, and published // and then FR is available, and published
content.SetCultureName("name-fr", langFr); content.SetCultureName("name-fr", langFr);
Assert.IsTrue(content.PublishCulture(langFr)); Assert.IsTrue(content.PublishCulture(CultureType.Single(langFr, false)));
// now UK is available too // now UK is available too
content.SetCultureName("name-uk", langUk); content.SetCultureName("name-uk", langUk);

View File

@@ -181,13 +181,13 @@ namespace Umbraco.Tests.Persistence.NPocoTests
contentTypeService.Save(contentType); contentTypeService.Save(contentType);
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1); var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); content1.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
content1.PublishCulture(); content1.PublishCulture(CultureType.Invariant);
contentService.SaveAndPublish(content1); contentService.SaveAndPublish(content1);
id2 = content1.Id; id2 = content1.Id;
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1); var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1);
content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" }); content2.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
content2.PublishCulture(); content2.PublishCulture(CultureType.Invariant);
contentService.SaveAndPublish(content2); contentService.SaveAndPublish(content2);
id3 = content2.Id; id3 = content2.Id;

View File

@@ -141,8 +141,8 @@ namespace Umbraco.Tests.Persistence.Repositories
// publish = new edit version // publish = new edit version
content1.SetValue("title", "title"); content1.SetValue("title", "title");
((Content)content1).PublishCulture(); content1.PublishCulture(CultureType.Invariant);
((Content)content1).PublishedState = PublishedState.Publishing; content1.PublishedState = PublishedState.Publishing;
repository.Save(content1); repository.Save(content1);
versions.Add(content1.VersionId); // NEW VERSION versions.Add(content1.VersionId); // NEW VERSION
@@ -203,8 +203,8 @@ namespace Umbraco.Tests.Persistence.Repositories
Assert.AreEqual(false, scope.Database.ExecuteScalar<bool>($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id })); Assert.AreEqual(false, scope.Database.ExecuteScalar<bool>($"SELECT published FROM {Constants.DatabaseSchema.Tables.Document} WHERE nodeId=@id", new { id = content1.Id }));
// publish = version // publish = version
((Content)content1).PublishCulture(); content1.PublishCulture(CultureType.Invariant);
((Content)content1).PublishedState = PublishedState.Publishing; content1.PublishedState = PublishedState.Publishing;
repository.Save(content1); repository.Save(content1);
versions.Add(content1.VersionId); // NEW VERSION versions.Add(content1.VersionId); // NEW VERSION
@@ -239,8 +239,8 @@ namespace Umbraco.Tests.Persistence.Repositories
// publish = new version // publish = new version
content1.Name = "name-4"; content1.Name = "name-4";
content1.SetValue("title", "title-4"); content1.SetValue("title", "title-4");
((Content)content1).PublishCulture(); content1.PublishCulture(CultureType.Invariant);
((Content)content1).PublishedState = PublishedState.Publishing; content1.PublishedState = PublishedState.Publishing;
repository.Save(content1); repository.Save(content1);
versions.Add(content1.VersionId); // NEW VERSION versions.Add(content1.VersionId); // NEW VERSION
@@ -654,7 +654,7 @@ namespace Umbraco.Tests.Persistence.Repositories
// publish them all // publish them all
foreach (var content in result) foreach (var content in result)
{ {
content.PublishCulture(); content.PublishCulture(CultureType.Invariant);
repository.Save(content); repository.Save(content);
} }

View File

@@ -517,7 +517,7 @@ namespace Umbraco.Tests.Services
allTags = tagService.GetAllContentTags(); allTags = tagService.GetAllContentTags();
Assert.AreEqual(0, allTags.Count()); Assert.AreEqual(0, allTags.Count());
content1.PublishCulture(); content1.PublishCulture(CultureType.Invariant);
contentService.SaveAndPublish(content1); contentService.SaveAndPublish(content1);
Assert.IsTrue(content1.Published); Assert.IsTrue(content1.Published);
@@ -601,7 +601,7 @@ namespace Umbraco.Tests.Services
var allTags = tagService.GetAllContentTags(); var allTags = tagService.GetAllContentTags();
Assert.AreEqual(0, allTags.Count()); Assert.AreEqual(0, allTags.Count());
content1.PublishCulture(); content1.PublishCulture(CultureType.Invariant);
contentService.SaveAndPublish(content1); contentService.SaveAndPublish(content1);
tags = tagService.GetTagsForEntity(content2.Id); tags = tagService.GetTagsForEntity(content2.Id);

View File

@@ -732,8 +732,8 @@ namespace Umbraco.Tests.Services
IContent content = new Content("content", Constants.System.Root, contentType); IContent content = new Content("content", Constants.System.Root, contentType);
content.SetCultureName("content-fr", langFr.IsoCode); content.SetCultureName("content-fr", langFr.IsoCode);
content.SetCultureName("content-en", langUk.IsoCode); content.SetCultureName("content-en", langUk.IsoCode);
content.PublishCulture(langFr.IsoCode); content.PublishCulture(CultureType.Single(langFr.IsoCode, langFr.IsDefault));
content.PublishCulture(langUk.IsoCode); content.PublishCulture(CultureType.Single(langUk.IsoCode, langUk.IsDefault));
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode)); Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode)); Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
@@ -988,7 +988,7 @@ namespace Umbraco.Tests.Services
// content cannot publish values because they are invalid // content cannot publish values because they are invalid
var propertyValidationService = new PropertyValidationService(Factory.GetInstance<PropertyEditorCollection>(), ServiceContext.DataTypeService); var propertyValidationService = new PropertyValidationService(Factory.GetInstance<PropertyEditorCollection>(), ServiceContext.DataTypeService);
var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties); var isValid = propertyValidationService.IsPropertyDataValid(content, out var invalidProperties, CultureType.Invariant);
Assert.IsFalse(isValid); Assert.IsFalse(isValid);
Assert.IsNotEmpty(invalidProperties); Assert.IsNotEmpty(invalidProperties);
@@ -1018,7 +1018,7 @@ namespace Umbraco.Tests.Services
content.SetCultureName("name-fr", langFr.IsoCode); content.SetCultureName("name-fr", langFr.IsoCode);
content.SetCultureName("name-da", langDa.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); var result = ((ContentService)ServiceContext.ContentService).CommitDocumentChanges(content);
Assert.IsTrue(result.Success); Assert.IsTrue(result.Success);
content = ServiceContext.ContentService.GetById(content.Id); content = ServiceContext.ContentService.GetById(content.Id);
@@ -1026,7 +1026,7 @@ namespace Umbraco.Tests.Services
Assert.IsFalse(content.IsCulturePublished(langDa.IsoCode)); Assert.IsFalse(content.IsCulturePublished(langDa.IsoCode));
content.UnpublishCulture(langFr.IsoCode); content.UnpublishCulture(langFr.IsoCode);
content.PublishCulture(langDa.IsoCode); content.PublishCulture(CultureType.Single(langDa.IsoCode, langDa.IsDefault));
result = ((ContentService)ServiceContext.ContentService).CommitDocumentChanges(content); result = ((ContentService)ServiceContext.ContentService).CommitDocumentChanges(content);
Assert.IsTrue(result.Success); Assert.IsTrue(result.Success);

View File

@@ -216,7 +216,7 @@ namespace Umbraco.Tests.Services
var result = new List<IContent>(); var result = new List<IContent>();
ServiceContext.ContentTypeService.Save(contentType1); ServiceContext.ContentTypeService.Save(contentType1);
IContent lastParent = MockedContent.CreateSimpleContent(contentType1); IContent lastParent = MockedContent.CreateSimpleContent(contentType1);
lastParent.PublishCulture(); lastParent.PublishCulture(CultureType.Invariant);
ServiceContext.ContentService.SaveAndPublish(lastParent); ServiceContext.ContentService.SaveAndPublish(lastParent);
result.Add(lastParent); result.Add(lastParent);
//create 20 deep //create 20 deep
@@ -230,7 +230,7 @@ namespace Umbraco.Tests.Services
//only publish evens //only publish evens
if (j % 2 == 0) if (j % 2 == 0)
{ {
content.PublishCulture(); content.PublishCulture(CultureType.Invariant);
ServiceContext.ContentService.SaveAndPublish(content); ServiceContext.ContentService.SaveAndPublish(content);
} }
else else

View File

@@ -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);
})();

View File

@@ -0,0 +1,8 @@
<span class="db" ng-repeat="notification in vm.notifications">
<!-- These are server side notifications for messages, generally success/warning, validation messages handled separately -->
<!-- Success = 3, Error = 2, Warning = 4 -->
<span class="db umb-permission__description"
ng-class="{'text-success': notification.type === 3, 'text-error': notification.type === 2 || notification.type === 4}">
{{notification.message}}
</span>
</span>

View File

@@ -36,10 +36,8 @@
<span class="db umb-permission__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span> <span class="db umb-permission__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span> </span>
<span class="db" ng-repeat="notification in variant.notifications"> <umb-notification-list notifications="variant.notifications" />
<!-- These are server side notifications for successful messages, non successful messages are returned via validation errors -->
<span class="db umb-permission__description text-success">{{notification.message}}</span>
</span>
</label> </label>
</div> </div>
</div> </div>

View File

@@ -64,10 +64,8 @@
<span class="db umb-permission__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span> <span class="db umb-permission__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span> </span>
<span class="db" ng-repeat="notification in variant.notifications"> <umb-notification-list notifications="variant.notifications" />
<!-- These are server side notifications for successful messages, non successful messages are returned via validation errors -->
<span class="db umb-permission__description text-success">{{notification.message}}</span>
</span>
</label> </label>
</div> </div>
</div> </div>

View File

@@ -41,10 +41,8 @@
<span class="db umb-permission__description text-error" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span> <span class="db umb-permission__description text-error" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span>
</span> </span>
<span class="db" ng-repeat="notification in variant.notifications"> <umb-notification-list notifications="variant.notifications" />
<!-- These are server side notifications for successful messages, non successful messages are returned via validation errors -->
<span class="db umb-permission__description text-success">{{notification.message}}</span>
</span>
</label> </label>
</div> </div>
</div> </div>

View File

@@ -170,10 +170,7 @@
<div class="umb-permission__description text-error" ng-message="valServerField">{{scheduleSelectorForm.saveVariantReleaseDate.errorMsg}}</div> <div class="umb-permission__description text-error" ng-message="valServerField">{{scheduleSelectorForm.saveVariantReleaseDate.errorMsg}}</div>
</div> </div>
<div ng-repeat="notification in variant.notifications"> <umb-notification-list notifications="variant.notifications" />
<!-- These are server side notifications for successful messages, non successful messages are returned via validation errors -->
<div class="umb-permission__description text-success">{{notification.message}}</div>
</div>
</div> </div>
</div> </div>

View File

@@ -34,9 +34,8 @@
<span class="db umb-permission__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span> <span class="db umb-permission__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span> </span>
<span class="db" ng-repeat="notification in variant.notifications"> <umb-notification-list notifications="variant.notifications" />
<span class="db umb-permission__description text-success">{{notification.message}}</span>
</span>
</label> </label>
</div> </div>
</div> </div>

View File

@@ -728,7 +728,7 @@ namespace Umbraco.Web.Editors
break; break;
} }
var publishStatus = PublishBranchInternal(contentItem, false, out wasCancelled, out var successfulCultures); var publishStatus = PublishBranchInternal(contentItem, false, out wasCancelled, out var successfulCultures).ToList();
//global notifications //global notifications
AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures);
@@ -749,7 +749,7 @@ namespace Umbraco.Web.Editors
break; break;
} }
var publishStatus = PublishBranchInternal(contentItem, true, out wasCancelled, out var successfulCultures); var publishStatus = PublishBranchInternal(contentItem, true, out wasCancelled, out var successfulCultures).ToList();
//global notifications //global notifications
AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures); AddMessageForPublishStatus(publishStatus, globalNotifications, successfulCultures);
@@ -1341,7 +1341,7 @@ namespace Umbraco.Web.Editors
foreach (var variant in cultureVariants.Where(x => x.Publish)) foreach (var variant in cultureVariants.Where(x => x.Publish))
{ {
// publishing any culture, implies the invariant culture // 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) if (!valid)
{ {
AddCultureValidationError(variant.Culture, "speechBubbles/contentCultureValidationError"); AddCultureValidationError(variant.Culture, "speechBubbles/contentCultureValidationError");
@@ -1942,12 +1942,12 @@ namespace Umbraco.Web.Editors
/// <summary> /// <summary>
/// Adds notification messages to the outbound display model for a given published status /// Adds notification messages to the outbound display model for a given published status
/// </summary> /// </summary>
/// <param name="status"></param> /// <param name="statuses"></param>
/// <param name="display"></param> /// <param name="display"></param>
/// <param name="successfulCultures"> /// <param name="successfulCultures">
/// This is null when dealing with invariant content, else it's the cultures that were successfully published /// This is null when dealing with invariant content, else it's the cultures that were successfully published
/// </param> /// </param>
private void AddMessageForPublishStatus(IEnumerable<PublishResult> statuses, INotificationModel display, string[] successfulCultures = null) private void AddMessageForPublishStatus(IReadOnlyCollection<PublishResult> statuses, INotificationModel display, string[] successfulCultures = null)
{ {
var totalStatusCount = statuses.Count(); var totalStatusCount = statuses.Count();
@@ -2046,7 +2046,7 @@ namespace Umbraco.Web.Editors
break; break;
case PublishResultType.FailedPublishPathNotPublished: 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( display.AddWarningNotification(
Services.TextService.Localize("publish"), Services.TextService.Localize("publish"),
Services.TextService.Localize("publish/contentPublishedFailedByParent", Services.TextService.Localize("publish/contentPublishedFailedByParent",
@@ -2055,13 +2055,13 @@ namespace Umbraco.Web.Editors
break; break;
case PublishResultType.FailedPublishCancelledByEvent: 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 }); AddCancelMessage(display, message: "publish/contentPublishedFailedByEvent", messageParams: new[] { names });
} }
break; break;
case PublishResultType.FailedPublishAwaitingRelease: 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( display.AddWarningNotification(
Services.TextService.Localize("publish"), Services.TextService.Localize("publish"),
Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease", Services.TextService.Localize("publish/contentPublishedFailedAwaitingRelease",
@@ -2070,7 +2070,7 @@ namespace Umbraco.Web.Editors
break; break;
case PublishResultType.FailedPublishHasExpired: 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( display.AddWarningNotification(
Services.TextService.Localize("publish"), Services.TextService.Localize("publish"),
Services.TextService.Localize("publish/contentPublishedFailedExpired", Services.TextService.Localize("publish/contentPublishedFailedExpired",
@@ -2079,7 +2079,7 @@ namespace Umbraco.Web.Editors
break; break;
case PublishResultType.FailedPublishIsTrashed: 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( display.AddWarningNotification(
Services.TextService.Localize("publish"), Services.TextService.Localize("publish"),
Services.TextService.Localize("publish/contentPublishedFailedIsTrashed", Services.TextService.Localize("publish/contentPublishedFailedIsTrashed",
@@ -2088,7 +2088,7 @@ namespace Umbraco.Web.Editors
break; break;
case PublishResultType.FailedPublishContentInvalid: 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( display.AddWarningNotification(
Services.TextService.Localize("publish"), Services.TextService.Localize("publish"),
Services.TextService.Localize("publish/contentPublishedFailedInvalid", Services.TextService.Localize("publish/contentPublishedFailedInvalid",
@@ -2106,6 +2106,16 @@ namespace Umbraco.Web.Editors
} }
} }
/// <summary>
/// Returns true if the culture specified is the default culture
/// </summary>
/// <param name="culture"></param>
/// <returns></returns>
private bool IsDefaultCulture(string culture)
{
return _allLangs.Value.Any(x => x.Value.IsDefault && x.Key.InvariantEquals(culture));
}
/// <summary> /// <summary>
/// Used to map an <see cref="IContent"/> instance to a <see cref="ContentItemDisplay"/> and ensuring a language is present if required /// Used to map an <see cref="IContent"/> instance to a <see cref="ContentItemDisplay"/> and ensuring a language is present if required
/// </summary> /// </summary>