diff --git a/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs b/src/Umbraco.Core/Models/ContentRepositoryExtensions.cs
index c0e4cd70321f..bf7228ca4795 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
{
@@ -170,23 +166,23 @@ public static void SetCultureInfo(this IContentBase content, string culture, str
/// Sets the publishing values for names and properties.
///
///
- ///
+ ///
/// 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, CultureImpact impact)
{
- culture = culture.NullOrWhiteSpaceAsNull();
+ if (impact == null) throw new ArgumentNullException(nameof(impact));
// 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))
- throw new NotSupportedException($"Culture \"{culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
+ if (!content.ContentType.SupportsPropertyVariation(impact.Culture, "*", true))
+ throw new NotSupportedException($"Culture \"{impact.Culture}\" is not supported by content type \"{content.ContentType.Alias}\" with variation \"{content.ContentType.Variations}\".");
- var alsoInvariant = false;
- if (culture == "*") // all cultures
+ // set names
+ if (impact.ImpactsAllCultures)
{
- foreach (var c in content.AvailableCultures)
+ foreach (var c in content.AvailableCultures) // does NOT contain the invariant culture
{
var name = content.GetCultureName(c);
if (string.IsNullOrWhiteSpace(name))
@@ -194,26 +190,31 @@ public static bool PublishCulture(this IContent content, string culture = "*")
content.SetPublishInfo(c, name, DateTime.Now);
}
}
- else if (culture == null) // invariant culture
+ else if (impact.ImpactsOnlyInvariantCulture)
{
if (string.IsNullOrWhiteSpace(content.Name))
return false;
// PublishName set by repository - nothing to do here
}
- else // one single culture
+ else if (impact.ImpactsExplicitCulture)
{
- var name = content.GetCultureName(culture);
+ var name = content.GetCultureName(impact.Culture);
if (string.IsNullOrWhiteSpace(name))
return false;
- content.SetPublishInfo(culture, name, DateTime.Now);
- alsoInvariant = true; // we also want to publish invariant values
+ content.SetPublishInfo(impact.Culture, name, DateTime.Now);
}
- // property.PublishValues only publishes what is valid, variation-wise
+ // set values
+ // property.PublishValues only publishes what is valid, variation-wise,
+ // but accepts any culture arg: null, all, specific
foreach (var property in content.Properties)
{
- property.PublishValues(culture);
- if (alsoInvariant)
+ // for the specified culture (null or all or specific)
+ property.PublishValues(impact.Culture);
+
+ // maybe the specified culture did not impact the invariant culture, so PublishValues
+ // above would skip it, yet it *also* impacts invariant properties
+ if (impact.ImpactsAlsoInvariantProperties)
property.PublishValues(null);
}
diff --git a/src/Umbraco.Core/Models/CultureImpact.cs b/src/Umbraco.Core/Models/CultureImpact.cs
new file mode 100644
index 000000000000..0b035c170343
--- /dev/null
+++ b/src/Umbraco.Core/Models/CultureImpact.cs
@@ -0,0 +1,258 @@
+using System;
+using System.Linq;
+
+namespace Umbraco.Core.Models
+{
+ ///
+ /// Represents the impact of a culture set.
+ ///
+ ///
+ /// A set of cultures can be either all cultures (including the invariant culture), or
+ /// the invariant culture, or a specific culture.
+ ///
+ internal class CultureImpact
+ {
+ ///
+ /// Utility method to return the culture used for invariant property errors based on what cultures are being actively saved,
+ /// the default culture and the state of the current content item
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static string GetCultureForInvariantErrors(IContent content, string[] savingCultures, string defaultCulture)
+ {
+ if (content == null) throw new ArgumentNullException(nameof(content));
+ if (savingCultures == null) throw new ArgumentNullException(nameof(savingCultures));
+ if (savingCultures.Length == 0) throw new ArgumentException(nameof(savingCultures));
+ if (defaultCulture == null) throw new ArgumentNullException(nameof(defaultCulture));
+
+ var cultureForInvariantErrors = savingCultures.Any(x => x.InvariantEquals(defaultCulture))
+ //the default culture is being flagged for saving so use it
+ ? defaultCulture
+ //If the content has no published version, we need to affiliate validation with the first variant being saved.
+ //If the content has a published version we will not affiliate the validation with any culture (null)
+ : !content.Published ? savingCultures[0] : null;
+
+ return cultureForInvariantErrors;
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The culture code.
+ /// A value indicating whether the culture is the default culture.
+ private CultureImpact(string culture, bool isDefault = false)
+ {
+ if (culture != null && culture.IsNullOrWhiteSpace())
+ throw new ArgumentException("Culture \"\" is not valid here.");
+
+ Culture = culture;
+
+ if ((culture == null || culture == "*") && isDefault)
+ throw new ArgumentException("The invariant or 'all' culture can not be the default culture.");
+
+ ImpactsOnlyDefaultCulture = isDefault;
+ }
+
+ ///
+ /// Gets the impact of 'all' cultures (including the invariant culture).
+ ///
+ public static CultureImpact All { get; } = new CultureImpact("*");
+
+ ///
+ /// Gets the impact of the invariant culture.
+ ///
+ public static CultureImpact Invariant { get; } = new CultureImpact(null);
+
+ ///
+ /// Creates an impact instance representing the impact of a specific culture.
+ ///
+ /// The culture code.
+ /// A value indicating whether the culture is the default culture.
+ public static CultureImpact Explicit(string culture, bool isDefault)
+ {
+ if (culture == null)
+ throw new ArgumentException("Culture is not explicit.");
+ if (culture.IsNullOrWhiteSpace())
+ throw new ArgumentException("Culture \"\" is not explicit.");
+ if (culture == "*")
+ throw new ArgumentException("Culture \"*\" is not explicit.");
+
+ return new CultureImpact(culture, isDefault);
+ }
+
+ ///
+ /// Creates an impact instance representing the impact of a culture set,
+ /// in the context of a content item variation.
+ ///
+ /// The culture code.
+ /// A value indicating whether the culture is the default culture.
+ /// The content item.
+ ///
+ /// Validates that the culture is compatible with the variation.
+ ///
+ public static CultureImpact Create(string culture, bool isDefault, IContent content)
+ {
+ // throws if not successful
+ TryCreate(culture, isDefault, content.ContentType.Variations, true, out var impact);
+ return impact;
+ }
+
+ ///
+ /// Tries to create an impact instance representing the impact of a culture set,
+ /// in the context of a content item variation.
+ ///
+ /// The culture code.
+ /// A value indicating whether the culture is the default culture.
+ /// A content variation.
+ /// A value indicating whether to throw if the impact cannot be created.
+ /// The impact if it could be created, otherwise null.
+ /// A value indicating whether the impact could be created.
+ ///
+ /// Validates that the culture is compatible with the variation.
+ ///
+ internal static bool TryCreate(string culture, bool isDefault, ContentVariation variation, bool throwOnFail, out CultureImpact impact)
+ {
+ impact = null;
+
+ // if culture is invariant...
+ if (culture == null)
+ {
+ // ... then variation must not vary by culture ...
+ if (variation.VariesByCulture())
+ {
+ if (throwOnFail)
+ throw new InvalidOperationException("The invariant culture is not compatible with a varying variation.");
+ return false;
+ }
+
+ // ... and it cannot be default
+ if (isDefault)
+ {
+ if (throwOnFail)
+ throw new InvalidOperationException("The invariant culture can not be the default culture.");
+ return false;
+ }
+
+ impact = Invariant;
+ return true;
+ }
+
+ // if culture is 'all'...
+ if (culture == "*")
+ {
+ // ... it cannot be default
+ if (isDefault)
+ {
+ if (throwOnFail)
+ throw new InvalidOperationException("The 'all' culture can not be the default culture.");
+ return false;
+ }
+
+ // if variation does not vary by culture, then impact is invariant
+ impact = variation.VariesByCulture() ? All : Invariant;
+ return true;
+ }
+
+ // neither null nor "*" - cannot be the empty string
+ if (culture.IsNullOrWhiteSpace())
+ {
+ if (throwOnFail)
+ throw new ArgumentException("Cannot be the empty string.", nameof(culture));
+ return false;
+ }
+
+ // if culture is specific, then variation must vary
+ if (!variation.VariesByCulture())
+ {
+ if (throwOnFail)
+ throw new InvalidOperationException($"The variant culture {culture} is not compatible with an invariant variation.");
+ return false;
+ }
+
+ // return specific impact
+ impact = new CultureImpact(culture, isDefault);
+ return true;
+ }
+
+ ///
+ /// Gets the culture code.
+ ///
+ ///
+ /// Can be null (invariant) or * (all cultures) or a specific culture code.
+ ///
+ public string Culture { get; }
+
+ ///
+ /// Gets a value indicating whether this impact impacts all cultures, including,
+ /// indirectly, the invariant culture.
+ ///
+ public bool ImpactsAllCultures => Culture == "*";
+
+ ///
+ /// Gets a value indicating whether this impact impacts only the invariant culture,
+ /// directly, not because all cultures are impacted.
+ ///
+ public bool ImpactsOnlyInvariantCulture => Culture == null;
+
+ ///
+ /// Gets a value indicating whether this impact impacts an implicit culture.
+ ///
+ /// And then it does not impact the invariant culture. The impacted
+ /// explicit culture could be the default culture.
+ public bool ImpactsExplicitCulture => Culture != null && Culture != "*";
+
+ ///
+ /// Gets a value indicating whether this impact impacts the default culture, directly,
+ /// not because all cultures are impacted.
+ ///
+ public bool ImpactsOnlyDefaultCulture {get; }
+
+ ///
+ /// Gets a value indicating whether this impact impacts the invariant properties, either
+ /// directly, or because all cultures are impacted, or because the default culture is impacted.
+ ///
+ public bool ImpactsInvariantProperties => Culture == null || Culture == "*" || ImpactsOnlyDefaultCulture;
+
+ ///
+ /// Gets a value indicating whether this also impact impacts the invariant properties,
+ /// even though it does not impact the invariant culture, neither directly (ImpactsInvariantCulture)
+ /// nor indirectly (ImpactsAllCultures).
+ ///
+ public bool ImpactsAlsoInvariantProperties => !ImpactsOnlyInvariantCulture &&
+ !ImpactsAllCultures &&
+ ImpactsOnlyDefaultCulture;
+
+ public Behavior CultureBehavior
+ {
+ get
+ {
+ //null can only be invariant
+ if (Culture == null) return Behavior.InvariantCulture | Behavior.InvariantProperties;
+
+ // * is All which means its also invariant properties since this will include the default language
+ if (Culture == "*") return (Behavior.AllCultures | Behavior.InvariantProperties);
+
+ //else it's explicit
+ var result = Behavior.ExplicitCulture;
+
+ //if the explicit culture is the default, then the behavior is also InvariantProperties
+ if (ImpactsOnlyDefaultCulture)
+ result |= Behavior.InvariantProperties;
+
+ return result;
+ }
+ }
+
+
+ [Flags]
+ public enum Behavior : byte
+ {
+ AllCultures = 1,
+ InvariantCulture = 2,
+ ExplicitCulture = 4,
+ InvariantProperties = 8
+ }
+ }
+}
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 000000000000..b48b5588de43
--- /dev/null
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepositoryExtensions.cs
@@ -0,0 +1,11 @@
+namespace Umbraco.Core.Persistence.Repositories.Implement
+{
+ internal static class LanguageRepositoryExtensions
+ {
+ public static bool IsDefault(this ILanguageRepository repo, string culture)
+ {
+ if (culture == null || culture == "*") return false;
+ return repo.GetDefaultIsoCode().InvariantEquals(culture);
+ }
+ }
+}
diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs
index 5e1e0529b697..a4def1d209d7 100644
--- a/src/Umbraco.Core/Services/Implement/ContentService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentService.cs
@@ -10,7 +10,7 @@
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
-using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.Persistence.Repositories.Implement;
using Umbraco.Core.Scoping;
using Umbraco.Core.Services.Changes;
@@ -505,7 +505,7 @@ public IEnumerable GetVersionsSlim(int id, int skip, int take)
///
public IEnumerable GetVersionIds(int id, int maxRows)
{
- using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ using (ScopeProvider.CreateScope(autoComplete: true))
{
return _documentRepository.GetVersionIds(id, maxRows);
}
@@ -877,38 +877,15 @@ public PublishResult SaveAndPublish(IContent content, string culture = "*", int
if (raiseEvents && scope.Events.DispatchCancelable(Saving, this, saveEventArgs, nameof(Saving)))
return new PublishResult(PublishResultType.FailedPublishCancelledByEvent, evtMsgs, content);
- Property[] invalidProperties;
-
// if culture is specific, first publish the invariant values, then publish the culture itself.
// if culture is '*', then publish them all (including variants)
- // explicitly SaveAndPublish a specific culture also publishes invariant values
- if (!culture.IsNullOrWhiteSpace() && culture != "*")
- {
- // publish the invariant values
- 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))
- return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
- {
- InvalidProperties = invalidProperties
- };
- }
+ //this will create the correct culture impact even if culture is * or null
+ var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
// publish the culture(s)
- var publishCulture = content.PublishCulture(culture);
- if (!publishCulture)
- return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
-
- //validate the property values
- if (!_propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties))
- return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
- {
- InvalidProperties = invalidProperties
- };
+ // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
+ content.PublishCulture(impact);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
scope.Complete();
@@ -941,16 +918,15 @@ public PublishResult SaveAndPublish(IContent content, string[] cultures, int use
: new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
}
+ if (cultures.Any(x => x == null || x == "*"))
+ throw new InvalidOperationException("Only valid cultures are allowed to be used in this method, wildcards or nulls are not allowed");
- if (cultures.Select(content.PublishCulture).Any(isValid => !isValid))
- return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
+ var impacts = cultures.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x)));
- //validate the property values
- if (!_propertyValidationService.Value.IsPropertyDataValid(content, out var invalidProperties))
- return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
- {
- InvalidProperties = invalidProperties
- };
+ // publish the culture(s)
+ // we don't care about the response here, this response will be rechecked below but we need to set the culture info values now.
+ foreach (var impact in impacts)
+ content.PublishCulture(impact);
var result = CommitDocumentChangesInternal(scope, content, saveEventArgs, userId, raiseEvents);
scope.Complete();
@@ -1096,6 +1072,7 @@ private PublishResult CommitDocumentChangesInternal(IScope scope, IContent conte
if (publishing)
{
+ //determine cultures publishing/unpublishing which will be based on previous calls to content.PublishCulture and ClearPublishInfo
culturesUnpublishing = content.GetCulturesUnpublishing();
culturesPublishing = variesByCulture
? content.PublishCultureInfos.Values.Where(x => x.IsDirty()).Select(x => x.Culture).ToList()
@@ -1147,7 +1124,7 @@ private PublishResult CommitDocumentChangesInternal(IScope scope, IContent conte
// note: This unpublishes the entire document (not different variants)
unpublishResult = StrategyCanUnpublish(scope, content, evtMsgs);
if (unpublishResult.Success)
- unpublishResult = StrategyUnpublish(scope, content, userId, evtMsgs);
+ unpublishResult = StrategyUnpublish(content, evtMsgs);
else
{
// reset published state from temp values (publishing, unpublishing) to original value
@@ -1330,7 +1307,8 @@ private IEnumerable PerformScheduledPublishInternal(DateTime date
//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 impact = CultureImpact.Explicit(culture, _languageRepository.IsDefault(culture));
+ var tryPublish = d.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(d, out invalidProperties, impact);
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)));
@@ -1426,9 +1404,17 @@ private bool SaveAndPublishBranch_PublishCultures(IContent content, HashSet content.PublishCulture(culture) && _propertyValidationService.Value.IsPropertyDataValid(content, out _))
- : content.PublishCulture() && _propertyValidationService.Value.IsPropertyDataValid(content, out _);
+ if (content.ContentType.VariesByCulture())
+ {
+ return culturesToPublish.All(culture =>
+ {
+ var impact = CultureImpact.Create(culture, _languageRepository.IsDefault(culture), content);
+ return content.PublishCulture(impact) && _propertyValidationService.Value.IsPropertyDataValid(content, out _, impact);
+ });
+ }
+
+ return content.PublishCulture(CultureImpact.Invariant)
+ && _propertyValidationService.Value.IsPropertyDataValid(content, out _, CultureImpact.Invariant);
}
// utility 'ShouldPublish' func used by SaveAndPublishBranch
@@ -2500,10 +2486,29 @@ private PublishResult StrategyCanPublish(IScope scope, IContent content, bool ch
var variesByCulture = content.ContentType.VariesByCulture();
- //First check if mandatory languages fails, if this fails it will mean anything that the published flag on the document will
+ var impactsToPublish = culturesPublishing == null
+ ? new[] {CultureImpact.Invariant} //if it's null it's invariant
+ : culturesPublishing.Select(x => CultureImpact.Explicit(x, _languageRepository.IsDefault(x))).ToArray();
+
+ // publish the culture(s)
+ if (!impactsToPublish.All(content.PublishCulture))
+ return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content);
+
+ //validate the property values
+ Property[] invalidProperties = null;
+ if (!impactsToPublish.All(x => _propertyValidationService.Value.IsPropertyDataValid(content, out invalidProperties, x)))
+ return new PublishResult(PublishResultType.FailedPublishContentInvalid, evtMsgs, content)
+ {
+ InvalidProperties = invalidProperties
+ };
+
+ //Check if mandatory languages fails, if this fails it will mean anything that the published flag on the document will
// be changed to Unpublished and any culture currently published will not be visible.
if (variesByCulture)
{
+ if (culturesPublishing == null)
+ throw new InvalidOperationException("Internal error, variesByCulture but culturesPublishing is null.");
+
if (content.Published && culturesPublishing.Count == 0 && culturesUnpublishing.Count == 0) // no published cultures = cannot be published
return new PublishResult(PublishResultType.FailedPublishNothingToPublish, evtMsgs, content);
@@ -2639,15 +2644,13 @@ private PublishResult StrategyCanUnpublish(IScope scope, IContent content, Event
///
/// Unpublishes a document
///
- ///
///
- ///
///
///
///
/// It is assumed that all unpublishing checks have passed before calling this method like
///
- private PublishResult StrategyUnpublish(IScope scope, IContent content, int userId, EventMessages evtMsgs)
+ private PublishResult StrategyUnpublish(IContent content, EventMessages evtMsgs)
{
var attempt = new PublishResult(PublishResultType.SuccessUnpublish, evtMsgs, content);
@@ -2899,7 +2902,7 @@ public IContent CreateContentFromBlueprint(IContent blueprint, string name, int
public IEnumerable GetBlueprintsForContentTypes(params int[] contentTypeId)
{
- using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ using (ScopeProvider.CreateScope(autoComplete: true))
{
var query = Query();
if (contentTypeId.Length > 0)
diff --git a/src/Umbraco.Core/Services/PropertyValidationService.cs b/src/Umbraco.Core/Services/PropertyValidationService.cs
index 146173dd5c20..b846095bd1f8 100644
--- a/src/Umbraco.Core/Services/PropertyValidationService.cs
+++ b/src/Umbraco.Core/Services/PropertyValidationService.cs
@@ -31,30 +31,34 @@ public PropertyValidationService()
///
/// 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, CultureImpact impact)
{
// select invalid properties
invalidProperties = content.Properties.Where(x =>
{
- // if culture is null, we validate invariant properties only
- // if culture is '*' we validate both variant and invariant properties, automatically
- // if culture is specific eg 'en-US' we both too, but explicitly
+ var propertyTypeVaries = x.PropertyType.VariesByCulture();
- var varies = x.PropertyType.VariesByCulture();
+ // impacts invariant = validate invariant property, invariant culture
+ if (impact.ImpactsOnlyInvariantCulture)
+ return !(propertyTypeVaries || IsPropertyValid(x, null));
- if (culture == null)
- return !(varies || IsPropertyValid(x, null)); // validate invariant property, invariant culture
+ // impacts all = validate property, all cultures (incl. invariant)
+ if (impact.ImpactsAllCultures)
+ return !IsPropertyValid(x);
- if (culture == "*")
- return !IsPropertyValid(x, culture); // validate property, all cultures
+ // impacts explicit culture = validate variant property, explicit culture
+ if (propertyTypeVaries)
+ return !IsPropertyValid(x, impact.Culture);
- return varies
- ? !IsPropertyValid(x, culture) // validate variant property, explicit culture
- : !IsPropertyValid(x, null); // validate invariant property, explicit culture
- })
- .ToArray();
+ // and, for explicit culture, we may also have to validate invariant property, invariant culture
+ // if either
+ // - it is impacted (default culture), or
+ // - there is no published version of the content - maybe non-default culture, but no published version
+
+ var alsoInvariant = impact.ImpactsAlsoInvariantProperties || !content.Published;
+ return alsoInvariant && !IsPropertyValid(x, null);
+
+ }).ToArray();
return invalidProperties.Length == 0;
}
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index c4f5df9d5a39..a29cc3647a75 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -210,7 +210,9 @@
+
+
diff --git a/src/Umbraco.Tests/Models/ContentTests.cs b/src/Umbraco.Tests/Models/ContentTests.cs
index 05f726893e6f..311608766975 100644
--- a/src/Umbraco.Tests/Models/ContentTests.cs
+++ b/src/Umbraco.Tests/Models/ContentTests.cs
@@ -104,7 +104,7 @@ public void Variant_Published_Culture_Names_Track_Dirty_Changes()
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(CultureImpact.Explicit(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 @@ public void Variant_Published_Culture_Names_Track_Dirty_Changes()
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(CultureImpact.Explicit(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 @@ public void Can_Deep_Clone()
content.SetCultureName("Hello", "en-US");
content.SetCultureName("World", "es-ES");
- content.PublishCulture("en-US");
+ content.PublishCulture(CultureImpact.All);
// should not try to clone something that's not Published or Unpublished
// (and in fact it will not work)
@@ -414,7 +414,7 @@ public void Remember_Dirty_Properties()
content.SetCultureName("Hello", "en-US");
content.SetCultureName("World", "es-ES");
- content.PublishCulture("en-US");
+ content.PublishCulture(CultureImpact.All);
var i = 200;
foreach (var property in content.Properties)
diff --git a/src/Umbraco.Tests/Models/CultureImpactTests.cs b/src/Umbraco.Tests/Models/CultureImpactTests.cs
new file mode 100644
index 000000000000..6cad634a1472
--- /dev/null
+++ b/src/Umbraco.Tests/Models/CultureImpactTests.cs
@@ -0,0 +1,163 @@
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core.Models;
+
+namespace Umbraco.Tests.Models
+{
+ [TestFixture]
+ public class CultureImpactTests
+ {
+ [Test]
+ public void Get_Culture_For_Invariant_Errors()
+ {
+ var result = CultureImpact.GetCultureForInvariantErrors(
+ Mock.Of(x => x.Published == true),
+ new[] { "en-US", "fr-FR" },
+ "en-US");
+ Assert.AreEqual("en-US", result); //default culture is being saved so use it
+
+ result = CultureImpact.GetCultureForInvariantErrors(
+ Mock.Of(x => x.Published == false),
+ new[] { "fr-FR" },
+ "en-US");
+ Assert.AreEqual("fr-FR", result); //default culture not being saved with not published version, use the first culture being saved
+
+ result = CultureImpact.GetCultureForInvariantErrors(
+ Mock.Of(x => x.Published == true),
+ new[] { "fr-FR" },
+ "en-US");
+ Assert.AreEqual(null, result); //default culture not being saved with published version, use null
+
+ }
+
+ [Test]
+ public void All_Cultures()
+ {
+ var impact = CultureImpact.All;
+
+ Assert.AreEqual(impact.Culture, "*");
+
+ Assert.IsTrue(impact.ImpactsInvariantProperties);
+ Assert.IsFalse(impact.ImpactsAlsoInvariantProperties);
+ Assert.IsFalse(impact.ImpactsOnlyInvariantCulture);
+ Assert.IsFalse(impact.ImpactsExplicitCulture);
+ Assert.IsTrue(impact.ImpactsAllCultures);
+ Assert.IsFalse(impact.ImpactsOnlyDefaultCulture);
+ }
+
+ [Test]
+ public void Invariant_Culture()
+ {
+ var impact = CultureImpact.Invariant;
+
+ Assert.AreEqual(impact.Culture, null);
+
+ Assert.IsTrue(impact.ImpactsInvariantProperties);
+ Assert.IsFalse(impact.ImpactsAlsoInvariantProperties);
+ Assert.IsTrue(impact.ImpactsOnlyInvariantCulture);
+ Assert.IsFalse(impact.ImpactsExplicitCulture);
+ Assert.IsFalse(impact.ImpactsAllCultures);
+ Assert.IsFalse(impact.ImpactsOnlyDefaultCulture);
+ }
+
+ [Test]
+ public void Explicit_Default_Culture()
+ {
+ var impact = CultureImpact.Explicit("en-US", true);
+
+ Assert.AreEqual(impact.Culture, "en-US");
+
+ Assert.IsTrue(impact.ImpactsInvariantProperties);
+ Assert.IsTrue(impact.ImpactsAlsoInvariantProperties);
+ Assert.IsFalse(impact.ImpactsOnlyInvariantCulture);
+ Assert.IsTrue(impact.ImpactsExplicitCulture);
+ Assert.IsFalse(impact.ImpactsAllCultures);
+ Assert.IsTrue(impact.ImpactsOnlyDefaultCulture);
+ }
+
+ [Test]
+ public void Explicit_NonDefault_Culture()
+ {
+ var impact = CultureImpact.Explicit("en-US", false);
+
+ Assert.AreEqual(impact.Culture, "en-US");
+
+ Assert.IsFalse(impact.ImpactsInvariantProperties);
+ Assert.IsFalse(impact.ImpactsAlsoInvariantProperties);
+ Assert.IsFalse(impact.ImpactsOnlyInvariantCulture);
+ Assert.IsTrue(impact.ImpactsExplicitCulture);
+ Assert.IsFalse(impact.ImpactsAllCultures);
+ Assert.IsFalse(impact.ImpactsOnlyDefaultCulture);
+ }
+
+ [Test]
+ public void TryCreate_Explicit_Default_Culture()
+ {
+ var success = CultureImpact.TryCreate("en-US", true, ContentVariation.Culture, false, out var impact);
+ Assert.IsTrue(success);
+
+ Assert.AreEqual(impact.Culture, "en-US");
+
+ Assert.IsTrue(impact.ImpactsInvariantProperties);
+ Assert.IsTrue(impact.ImpactsAlsoInvariantProperties);
+ Assert.IsFalse(impact.ImpactsOnlyInvariantCulture);
+ Assert.IsTrue(impact.ImpactsExplicitCulture);
+ Assert.IsFalse(impact.ImpactsAllCultures);
+ Assert.IsTrue(impact.ImpactsOnlyDefaultCulture);
+ }
+
+ [Test]
+ public void TryCreate_Explicit_NonDefault_Culture()
+ {
+ var success = CultureImpact.TryCreate("en-US", false, ContentVariation.Culture, false, out var impact);
+ Assert.IsTrue(success);
+
+ Assert.AreEqual(impact.Culture, "en-US");
+
+ Assert.IsFalse(impact.ImpactsInvariantProperties);
+ Assert.IsFalse(impact.ImpactsAlsoInvariantProperties);
+ Assert.IsFalse(impact.ImpactsOnlyInvariantCulture);
+ Assert.IsTrue(impact.ImpactsExplicitCulture);
+ Assert.IsFalse(impact.ImpactsAllCultures);
+ Assert.IsFalse(impact.ImpactsOnlyDefaultCulture);
+ }
+
+ [Test]
+ public void TryCreate_AllCultures_For_Invariant()
+ {
+ var success = CultureImpact.TryCreate("*", false, ContentVariation.Nothing, false, out var impact);
+ Assert.IsTrue(success);
+
+ Assert.AreEqual(impact.Culture, null);
+
+ Assert.AreSame(CultureImpact.Invariant, impact);
+ }
+
+ [Test]
+ public void TryCreate_AllCultures_For_Variant()
+ {
+ var success = CultureImpact.TryCreate("*", false, ContentVariation.Culture, false, out var impact);
+ Assert.IsTrue(success);
+
+ Assert.AreEqual(impact.Culture, "*");
+
+ Assert.AreSame(CultureImpact.All, impact);
+ }
+
+ [Test]
+ public void TryCreate_Invariant_For_Variant()
+ {
+ var success = CultureImpact.TryCreate(null, false, ContentVariation.Culture, false, out var impact);
+ Assert.IsFalse(success);
+ }
+
+ [Test]
+ public void TryCreate_Invariant_For_Invariant()
+ {
+ var success = CultureImpact.TryCreate(null, false, ContentVariation.Nothing, false, out var impact);
+ Assert.IsTrue(success);
+
+ Assert.AreSame(CultureImpact.Invariant, impact);
+ }
+ }
+}
diff --git a/src/Umbraco.Tests/Models/VariationTests.cs b/src/Umbraco.Tests/Models/VariationTests.cs
index 5569ecfa6002..8d64a6d28f2b 100644
--- a/src/Umbraco.Tests/Models/VariationTests.cs
+++ b/src/Umbraco.Tests/Models/VariationTests.cs
@@ -275,7 +275,7 @@ public void ContentPublishValues()
// can publish value
// and get edited and published values
- Assert.IsTrue(content.PublishCulture());
+ Assert.IsTrue(content.PublishCulture(CultureImpact.All));
Assert.AreEqual("a", content.GetValue("prop"));
Assert.AreEqual("a", content.GetValue("prop", published: true));
@@ -305,9 +305,9 @@ public void ContentPublishValues()
// can publish value
// and get edited and published values
- Assert.IsFalse(content.PublishCulture(langFr)); // no name
+ Assert.IsFalse(content.PublishCulture(CultureImpact.Explicit(langFr, false))); // no name
content.SetCultureName("name-fr", langFr);
- Assert.IsTrue(content.PublishCulture(langFr));
+ Assert.IsTrue(content.PublishCulture(CultureImpact.Explicit(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 @@ public void ContentPublishValues()
Assert.IsNull(content.GetValue("prop", langFr, published: true));
// can publish all
- Assert.IsTrue(content.PublishCulture("*"));
+ Assert.IsTrue(content.PublishCulture(CultureImpact.All));
Assert.IsNull(content.GetValue("prop"));
Assert.IsNull(content.GetValue("prop", published: true));
Assert.AreEqual("c", content.GetValue("prop", langFr));
@@ -331,14 +331,14 @@ public void ContentPublishValues()
content.UnpublishCulture(langFr);
Assert.AreEqual("c", content.GetValue("prop", langFr));
Assert.IsNull(content.GetValue("prop", langFr, published: true));
- content.PublishCulture(langFr);
+ Assert.IsTrue(content.PublishCulture(CultureImpact.Explicit(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
+ Assert.IsTrue(content.PublishCulture(CultureImpact.All)); // publishes invariant props if any
Assert.IsNull(content.GetValue("prop"));
Assert.IsNull(content.GetValue("prop", published: true));
@@ -384,15 +384,20 @@ public void ContentPublishValuesWithMixedPropertyTypeVariations()
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
+ //for this test we'll make the french culture the default one - this is needed for publishing invariant property values
+ var langFrImpact = CultureImpact.Explicit(langFr, true);
+
+ Assert.IsTrue(content.PublishCulture(langFrImpact)); // succeeds because names are ok (not validating properties here)
+ Assert.IsFalse(propertyValidationService.IsPropertyDataValid(content, out _, langFrImpact));// 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(langFrImpact)); // 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 _, langFrImpact));
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(langFrImpact)); // still ok...
+ Assert.IsTrue(propertyValidationService.IsPropertyDataValid(content, out _, langFrImpact));// now it's ok
Assert.AreEqual("a", content.GetValue("prop1", langFr, published: true));
Assert.AreEqual("x", content.GetValue("prop2", published: true));
@@ -423,12 +428,12 @@ public void ContentPublishVariations()
content.SetValue("prop", "a-es", langEs);
// cannot publish without a name
- Assert.IsFalse(content.PublishCulture(langFr));
+ Assert.IsFalse(content.PublishCulture(CultureImpact.Explicit(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(CultureImpact.Explicit(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 f558a6449908..5372a12ac211 100644
--- a/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs
+++ b/src/Umbraco.Tests/Persistence/NPocoTests/PetaPocoCachesTest.cs
@@ -181,13 +181,13 @@ private void CreateStuff(out int id1, out int id2, out int id3, out string alias
contentTypeService.Save(contentType);
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
content1.AssignTags("tags", new[] { "hello", "world", "some", "tags" });
- content1.PublishCulture();
+ content1.PublishCulture(CultureImpact.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(CultureImpact.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 2c4f3f190800..c7a83717a851 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/DocumentRepositoryTest.cs
@@ -141,8 +141,8 @@ public void CreateVersions()
// publish = new edit version
content1.SetValue("title", "title");
- ((Content)content1).PublishCulture();
- ((Content)content1).PublishedState = PublishedState.Publishing;
+ content1.PublishCulture(CultureImpact.Invariant);
+ content1.PublishedState = PublishedState.Publishing;
repository.Save(content1);
versions.Add(content1.VersionId); // NEW VERSION
@@ -203,8 +203,8 @@ public void CreateVersions()
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(CultureImpact.Invariant);
+ content1.PublishedState = PublishedState.Publishing;
repository.Save(content1);
versions.Add(content1.VersionId); // NEW VERSION
@@ -239,8 +239,8 @@ public void CreateVersions()
// publish = new version
content1.Name = "name-4";
content1.SetValue("title", "title-4");
- ((Content)content1).PublishCulture();
- ((Content)content1).PublishedState = PublishedState.Publishing;
+ content1.PublishCulture(CultureImpact.Invariant);
+ content1.PublishedState = PublishedState.Publishing;
repository.Save(content1);
versions.Add(content1.VersionId); // NEW VERSION
@@ -654,7 +654,7 @@ public void GetAllContentManyVersions()
// publish them all
foreach (var content in result)
{
- content.PublishCulture();
+ content.PublishCulture(CultureImpact.Invariant);
repository.Save(content);
}
diff --git a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs
index 5e97bea2c10c..6b4f2942f759 100644
--- a/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs
+++ b/src/Umbraco.Tests/Services/ContentServiceTagsTests.cs
@@ -517,7 +517,7 @@ public void TagsAreUpdatedWhenContentIsTrashedAndUnTrashed_Tree()
allTags = tagService.GetAllContentTags();
Assert.AreEqual(0, allTags.Count());
- content1.PublishCulture();
+ content1.PublishCulture(CultureImpact.Invariant);
contentService.SaveAndPublish(content1);
Assert.IsTrue(content1.Published);
@@ -601,7 +601,7 @@ public void TagsAreUpdatedWhenContentIsUnpublishedAndRePublished_Tree()
var allTags = tagService.GetAllContentTags();
Assert.AreEqual(0, allTags.Count());
- content1.PublishCulture();
+ content1.PublishCulture(CultureImpact.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 04cdc2aab708..7d8e000879db 100644
--- a/src/Umbraco.Tests/Services/ContentServiceTests.cs
+++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs
@@ -732,8 +732,8 @@ public void Can_Unpublish_Content_Variation()
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(CultureImpact.Explicit(langFr.IsoCode, langFr.IsDefault));
+ content.PublishCulture(CultureImpact.Explicit(langUk.IsoCode, langUk.IsDefault));
Assert.IsTrue(content.IsCulturePublished(langFr.IsoCode));
Assert.IsTrue(content.IsCulturePublished(langUk.IsoCode));
@@ -963,6 +963,18 @@ private void ContentServiceOnPublishing(IContentService sender, PublishEventArgs
Assert.AreEqual("Home", e.Name);
}
+ [Test]
+ public void Can_Not_Publish_Invalid_Cultures()
+ {
+ var contentService = ServiceContext.ContentService;
+ var content = Mock.Of(c => c.ContentType == Mock.Of(s => s.Variations == ContentVariation.Culture));
+
+ Assert.Throws(() => contentService.SaveAndPublish(content, new[] {"*"}));
+ Assert.Throws(() => contentService.SaveAndPublish(content, new string[] { null }));
+ Assert.Throws(() => contentService.SaveAndPublish(content, new[] { "*", null }));
+ Assert.Throws(() => contentService.SaveAndPublish(content, new[] { "en-US", "*", "es-ES" }));
+ }
+
[Test]
public void Can_Publish_Only_Valid_Content()
{
@@ -973,10 +985,7 @@ public void Can_Publish_Only_Valid_Content()
const int parentId = NodeDto.NodeIdSeed + 2;
var contentService = ServiceContext.ContentService;
- var content = MockedContent.CreateSimpleContent(contentType, "Invalid Content", parentId);
- content.SetValue("author", string.Empty);
- contentService.Save(content);
-
+
var parent = contentService.GetById(parentId);
var parentPublished = contentService.SaveAndPublish(parent);
@@ -986,9 +995,13 @@ public void Can_Publish_Only_Valid_Content()
Assert.IsTrue(parentPublished.Success);
Assert.IsTrue(parent.Published);
+ var content = MockedContent.CreateSimpleContent(contentType, "Invalid Content", parentId);
+ content.SetValue("author", string.Empty);
+ Assert.IsFalse(content.HasIdentity);
+
// 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, CultureImpact.Invariant);
Assert.IsFalse(isValid);
Assert.IsNotEmpty(invalidProperties);
@@ -998,7 +1011,12 @@ public void Can_Publish_Only_Valid_Content()
Assert.IsFalse(contentPublished.Success);
Assert.AreEqual(PublishResultType.FailedPublishContentInvalid, contentPublished.Result);
Assert.IsFalse(content.Published);
+
+ //Ensure it saved though
+ Assert.Greater(content.Id, 0);
+ Assert.IsTrue(content.HasIdentity);
}
+
[Test]
public void Can_Publish_And_Unpublish_Cultures_In_Single_Operation()
@@ -1018,7 +1036,7 @@ public void Can_Publish_And_Unpublish_Cultures_In_Single_Operation()
content.SetCultureName("name-fr", langFr.IsoCode);
content.SetCultureName("name-da", langDa.IsoCode);
- content.PublishCulture(langFr.IsoCode);
+ content.PublishCulture(CultureImpact.Explicit(langFr.IsoCode, langFr.IsDefault));
var result = ((ContentService)ServiceContext.ContentService).CommitDocumentChanges(content);
Assert.IsTrue(result.Success);
content = ServiceContext.ContentService.GetById(content.Id);
@@ -1026,7 +1044,7 @@ public void Can_Publish_And_Unpublish_Cultures_In_Single_Operation()
Assert.IsFalse(content.IsCulturePublished(langDa.IsoCode));
content.UnpublishCulture(langFr.IsoCode);
- content.PublishCulture(langDa.IsoCode);
+ content.PublishCulture(CultureImpact.Explicit(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 449e933c24b0..9cf38e17891f 100644
--- a/src/Umbraco.Tests/Services/PerformanceTests.cs
+++ b/src/Umbraco.Tests/Services/PerformanceTests.cs
@@ -216,7 +216,7 @@ private IEnumerable PrimeDbWithLotsOfContent()
var result = new List();
ServiceContext.ContentTypeService.Save(contentType1);
IContent lastParent = MockedContent.CreateSimpleContent(contentType1);
- lastParent.PublishCulture();
+ lastParent.PublishCulture(CultureImpact.Invariant);
ServiceContext.ContentService.SaveAndPublish(lastParent);
result.Add(lastParent);
//create 20 deep
@@ -230,7 +230,7 @@ private IEnumerable PrimeDbWithLotsOfContent()
//only publish evens
if (j % 2 == 0)
{
- content.PublishCulture();
+ content.PublishCulture(CultureImpact.Invariant);
ServiceContext.ContentService.SaveAndPublish(content);
}
else
diff --git a/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs b/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs
new file mode 100644
index 000000000000..e1e19918ceaa
--- /dev/null
+++ b/src/Umbraco.Tests/Services/PropertyValidationServiceTests.cs
@@ -0,0 +1,177 @@
+using System.Threading;
+using Moq;
+using NUnit.Framework;
+using Umbraco.Core;
+using Umbraco.Core.Models;
+using Umbraco.Core.PropertyEditors;
+using Umbraco.Core.PropertyEditors.Validators;
+using Umbraco.Core.Services;
+using Umbraco.Tests.Testing;
+using Umbraco.Web.PropertyEditors;
+
+namespace Umbraco.Tests.Services
+{
+ [TestFixture]
+ public class PropertyValidationServiceTests : UmbracoTestBase
+ {
+ private void MockObjects(out PropertyValidationService validationService, out IDataType dt)
+ {
+ var textService = new Mock();
+ textService.Setup(x => x.Localize(It.IsAny(), Thread.CurrentThread.CurrentCulture, null)).Returns("Localized text");
+
+ var dataTypeService = new Mock();
+ var dataType = Mock.Of(
+ x => x.Configuration == (object)string.Empty //irrelevant but needs a value
+ && x.DatabaseType == ValueStorageType.Nvarchar
+ && x.EditorAlias == Constants.PropertyEditors.Aliases.TextBox);
+ dataTypeService.Setup(x => x.GetDataType(It.IsAny())).Returns(() => dataType);
+ dt = dataType;
+
+ //new data editor that returns a TextOnlyValueEditor which will do the validation for the properties
+ var dataEditor = Mock.Of(
+ x => x.Type == EditorType.PropertyValue
+ && x.Alias == Constants.PropertyEditors.Aliases.TextBox);
+ Mock.Get(dataEditor).Setup(x => x.GetValueEditor(It.IsAny