From 1921fd8a76b77db02585f7b80ce2f632143cde1f Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 6 Oct 2021 11:37:03 +0200 Subject: [PATCH 01/32] Verify that root node has domains when multilingual content is published --- .../Controllers/ContentController.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index beebb246d9f2..63af3f4bc5f0 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -398,7 +398,7 @@ public ActionResult GetEmpty(string contentTypeAlias, int pa [HttpPost] public ActionResult> GetEmptyByAliases(ContentTypesByAliases contentTypesByAliases) { - // It's important to do this operation within a scope to reduce the amount of readlock queries. + // It's important to do this operation within a scope to reduce the amount of readlock queries. using var scope = _scopeProvider.CreateScope(autoComplete: true); var contentTypes = contentTypesByAliases.ContentTypeAliases.Select(alias => _contentTypeService.Get(alias)); return GetEmpties(contentTypes, contentTypesByAliases.ParentId).ToDictionary(x => x.ContentTypeAlias); @@ -1412,6 +1412,12 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul var publishStatus = _contentService.SaveAndPublish(contentItem.PersistedContent, culturesToPublish, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id); wasCancelled = publishStatus.Result == PublishResultType.FailedPublishCancelledByEvent; successfulCultures = culturesToPublish; + + // Verify that there's appropriate cultures configured for the root nude + if (publishStatus.Success && contentItem.ParentId == -1) + { + VerifyRootNodeDomainsForCultures(contentItem, publishStatus); + } return publishStatus; } else @@ -1425,6 +1431,33 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul } } + private void VerifyRootNodeDomainsForCultures(ContentItemSave contentItemSave, PublishResult publishResult) + { + IContent persistedContent = contentItemSave.PersistedContent; + var publishedCultures = persistedContent.PublishedCultures.ToList(); + // If only a single culture is published we shouldn't have any routing issues + if (publishedCultures.Count() < 2) + { + return; + } + + // If more than a single culture is published we need to verify that there's a domain registered for each published culture + IEnumerable assignedDomains = _domainService.GetAssignedDomains(persistedContent.Id, true); + + // No domains at all, add a warning, and making it scary, to add domains. + if (assignedDomains.Any() is false) + { + publishResult.EventMessages.Add(new EventMessage( + "Multilingual site", + "Domains are not configured for multilingual site, please contact an administrator, see log for more information", + EventMessageType.Error)); + + _logger.LogWarning("The root node {RootNodeName} is published with multiple cultures, but no domains are configured, this will cause routing and caching issues, please register domains for: {Cultures}", + persistedContent.Name, string.Join(", ", publishedCultures)); + return; + } + } + /// /// Validate if publishing is possible based on the mandatory language requirements /// From 740dfcc5312168c61e1cfc19abb9493dce8c1011 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 6 Oct 2021 12:26:18 +0200 Subject: [PATCH 02/32] Verify that published culture has domain --- .../Controllers/ContentController.cs | 33 ++++++++++++++----- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 63af3f4bc5f0..553bfd828c65 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -1413,10 +1413,10 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul wasCancelled = publishStatus.Result == PublishResultType.FailedPublishCancelledByEvent; successfulCultures = culturesToPublish; - // Verify that there's appropriate cultures configured for the root nude + // Verify that there's appropriate cultures configured for the root node if (publishStatus.Success && contentItem.ParentId == -1) { - VerifyRootNodeDomainsForCultures(contentItem, publishStatus); + VerifyDomainsForCultures(contentItem.PersistedContent, publishStatus); } return publishStatus; } @@ -1431,18 +1431,17 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul } } - private void VerifyRootNodeDomainsForCultures(ContentItemSave contentItemSave, PublishResult publishResult) + private void VerifyDomainsForCultures(IContent publishedContent, PublishResult publishResult) { - IContent persistedContent = contentItemSave.PersistedContent; - var publishedCultures = persistedContent.PublishedCultures.ToList(); + var publishedCultures = publishedContent.PublishedCultures.ToList(); // If only a single culture is published we shouldn't have any routing issues - if (publishedCultures.Count() < 2) + if (publishedCultures.Count < 2) { return; } // If more than a single culture is published we need to verify that there's a domain registered for each published culture - IEnumerable assignedDomains = _domainService.GetAssignedDomains(persistedContent.Id, true); + var assignedDomains = _domainService.GetAssignedDomains(publishedContent.Id, true).ToList(); // No domains at all, add a warning, and making it scary, to add domains. if (assignedDomains.Any() is false) @@ -1452,10 +1451,26 @@ private void VerifyRootNodeDomainsForCultures(ContentItemSave contentItemSave, P "Domains are not configured for multilingual site, please contact an administrator, see log for more information", EventMessageType.Error)); - _logger.LogWarning("The root node {RootNodeName} is published with multiple cultures, but no domains are configured, this will cause routing and caching issues, please register domains for: {Cultures}", - persistedContent.Name, string.Join(", ", publishedCultures)); + _logger.LogWarning("The root node {RootNodeName} was published with multiple cultures, but no domains are configured, this will cause routing and caching issues, please register domains for: {Cultures}", + publishedContent.Name, string.Join(", ", publishedCultures)); return; } + + // If there is some domains, verify that there's a domain for each of the published cultures + foreach (var culture in publishedCultures) + { + if (assignedDomains.Any(x => x.LanguageIsoCode.Equals(culture, StringComparison.OrdinalIgnoreCase)) is false) + { + publishResult.EventMessages.Add(new EventMessage( + "Multilingual site", + $"There is no domain configured for {culture}, please contact an administrator, see log for more information", + EventMessageType.Error + )); + + _logger.LogWarning("The root node {RootNodeName} was published in culture {Culture}, but there's no domain configured for it, this will cause routing and caching issues, please register a domain for it", + publishedContent.Name, culture); + } + } } /// From a60594440aa44ceb1030522966f0a0323e84082f Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 6 Oct 2021 12:57:21 +0200 Subject: [PATCH 03/32] Localize speech bubble text --- .../Controllers/ContentController.cs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 553bfd828c65..32165db609a8 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -1447,8 +1447,8 @@ private void VerifyDomainsForCultures(IContent publishedContent, PublishResult p if (assignedDomains.Any() is false) { publishResult.EventMessages.Add(new EventMessage( - "Multilingual site", - "Domains are not configured for multilingual site, please contact an administrator, see log for more information", + _localizedTextService.Localize("auditTrails", "publish"), + _localizedTextService.Localize("speechBubbles", "publishWithNoDomains"), EventMessageType.Error)); _logger.LogWarning("The root node {RootNodeName} was published with multiple cultures, but no domains are configured, this will cause routing and caching issues, please register domains for: {Cultures}", @@ -1457,19 +1457,17 @@ private void VerifyDomainsForCultures(IContent publishedContent, PublishResult p } // If there is some domains, verify that there's a domain for each of the published cultures - foreach (var culture in publishedCultures) + foreach (var culture in publishedCultures + .Where(culture => assignedDomains.Any(x => x.LanguageIsoCode.Equals(culture, StringComparison.OrdinalIgnoreCase)) is false)) { - if (assignedDomains.Any(x => x.LanguageIsoCode.Equals(culture, StringComparison.OrdinalIgnoreCase)) is false) - { - publishResult.EventMessages.Add(new EventMessage( - "Multilingual site", - $"There is no domain configured for {culture}, please contact an administrator, see log for more information", - EventMessageType.Error - )); - - _logger.LogWarning("The root node {RootNodeName} was published in culture {Culture}, but there's no domain configured for it, this will cause routing and caching issues, please register a domain for it", - publishedContent.Name, culture); - } + publishResult.EventMessages.Add(new EventMessage( + _localizedTextService.Localize("auditTrails", "publish"), + _localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{culture}), + EventMessageType.Error + )); + + _logger.LogWarning("The root node {RootNodeName} was published in culture {Culture}, but there's no domain configured for it, this will cause routing and caching issues, please register a domain for it", + publishedContent.Name, culture); } } From 9e05bf156fdcd88d79b24acad996f9a5da162d7d Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 6 Oct 2021 13:02:26 +0200 Subject: [PATCH 04/32] Add translations --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 ++ src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 05c2caad27da..3d62cd6be278 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1245,6 +1245,8 @@ Mange hilsner fra Umbraco robotten Kan ikke planlægge dokumentes udgivelse da det krævet '%0%' har en senere udgivelses dato end et ikke krævet sprog Afpubliceringsdatoen kan ikke ligge i fortiden Afpubliceringsdatoen kan ikke være før udgivelsesdatoen + Domæner er ikke konfigureret for en flersproget side, kontakt vensligst en administrator, se loggen for mere information + Der er ikke noget domæne konfigureret for %0%, kontakt vensligst en administrator, se loggen for mere information Tilføj style diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 06cc8de008d7..b5bd25446a3b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1470,6 +1470,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Cannot schedule the document for publishing since the required '%0%' has a publish date later than a non mandatory language The expire date cannot be in the past The expire date cannot be before the release date + Domains are not configured for multilingual site, please contact an administrator, see log for more information + There is no domain configured for %0%, please contact an administrator, see log for more information Add style From 7b6bf780ce28edc560e8a9f5f28f55bf45fac57c Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 6 Oct 2021 14:30:26 +0200 Subject: [PATCH 05/32] Add GetAncestorIds extension method --- src/Umbraco.Core/Extensions/ContentExtensions.cs | 14 +++++++++++--- .../Services/Implement/ContentService.cs | 16 +++++++++------- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Extensions/ContentExtensions.cs b/src/Umbraco.Core/Extensions/ContentExtensions.cs index 8385de5e7044..daca62926af1 100644 --- a/src/Umbraco.Core/Extensions/ContentExtensions.cs +++ b/src/Umbraco.Core/Extensions/ContentExtensions.cs @@ -3,15 +3,15 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.IO; using System.Linq; using System.Xml.Linq; +using Umbraco.Cms.Core; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Membership; -using Umbraco.Cms.Core.Models.PublishedContent; using Umbraco.Cms.Core.PropertyEditors; -using Umbraco.Cms.Core.Serialization; using Umbraco.Cms.Core.Services; using Umbraco.Cms.Core.Strings; @@ -165,7 +165,15 @@ public static ContentStatus GetStatus(this IContent content, string culture = nu return ContentStatus.Unpublished; } - + /// + /// Gets a collection containing the ids of all ancestors. + /// + /// to retrieve ancestors for + /// An Enumerable list of integer ids + public static IEnumerable GetAncestorIds(this IContent content) => + content.Path.Split(Constants.CharArrays.Comma) + .Where(x => x != Constants.System.RootString && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(s => + int.Parse(s, CultureInfo.InvariantCulture)); #endregion diff --git a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs index 15490bcd0448..21c365dd8e5f 100644 --- a/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs +++ b/src/Umbraco.Infrastructure/Services/Implement/ContentService.cs @@ -531,18 +531,20 @@ public IEnumerable GetAncestors(int id) public IEnumerable GetAncestors(IContent content) { //null check otherwise we get exceptions - if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty(); + if (content.Path.IsNullOrWhiteSpace()) + { + return Enumerable.Empty(); + } - var rootId = Cms.Core.Constants.System.RootString; - var ids = content.Path.Split(Constants.CharArrays.Comma) - .Where(x => x != rootId && x != content.Id.ToString(CultureInfo.InvariantCulture)).Select(s => - int.Parse(s, CultureInfo.InvariantCulture)).ToArray(); + var ids = content.GetAncestorIds().ToArray(); if (ids.Any() == false) + { return new List(); + } - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + using (IScope scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Cms.Core.Constants.Locks.ContentTree); + scope.ReadLock(Constants.Locks.ContentTree); return _documentRepository.GetMany(ids); } } From a8bd71ba7ea3ddd4a9ccb9c3c738530b55348d78 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 6 Oct 2021 14:31:42 +0200 Subject: [PATCH 06/32] Check ancestors for any configured domains --- .../Controllers/ContentController.cs | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 32165db609a8..c7fbfcaace87 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -1413,8 +1413,8 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul wasCancelled = publishStatus.Result == PublishResultType.FailedPublishCancelledByEvent; successfulCultures = culturesToPublish; - // Verify that there's appropriate cultures configured for the root node - if (publishStatus.Success && contentItem.ParentId == -1) + // Verify that there's appropriate cultures configured. + if (publishStatus.Success) { VerifyDomainsForCultures(contentItem.PersistedContent, publishStatus); } @@ -1431,9 +1431,18 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul } } - private void VerifyDomainsForCultures(IContent publishedContent, PublishResult publishResult) + /// + /// Verifies that there's an appropriate domain setup for the published cultures + /// + /// + /// Adds a warning and logs a message if, a node varies but culture, there's at least 1 culture already published, + /// and there's no domain added for the published cultures + /// + /// + /// + private void VerifyDomainsForCultures(IContent persistedContent, PublishResult publishResult) { - var publishedCultures = publishedContent.PublishedCultures.ToList(); + var publishedCultures = persistedContent.PublishedCultures.ToList(); // If only a single culture is published we shouldn't have any routing issues if (publishedCultures.Count < 2) { @@ -1441,18 +1450,24 @@ private void VerifyDomainsForCultures(IContent publishedContent, PublishResult p } // If more than a single culture is published we need to verify that there's a domain registered for each published culture - var assignedDomains = _domainService.GetAssignedDomains(publishedContent.Id, true).ToList(); + var assignedDomains = _domainService.GetAssignedDomains(persistedContent.Id, true).ToList(); + // We also have to check all of the ancestors, if any of those has the appropriate culture assigned we don't need to warn + foreach (var ancestorID in persistedContent.GetAncestorIds()) + { + assignedDomains.AddRange(_domainService.GetAssignedDomains(ancestorID, true)); + } + // No domains at all, add a warning, and making it scary, to add domains. - if (assignedDomains.Any() is false) + if (assignedDomains.Count == 0) { publishResult.EventMessages.Add(new EventMessage( _localizedTextService.Localize("auditTrails", "publish"), _localizedTextService.Localize("speechBubbles", "publishWithNoDomains"), - EventMessageType.Error)); + EventMessageType.Warning)); _logger.LogWarning("The root node {RootNodeName} was published with multiple cultures, but no domains are configured, this will cause routing and caching issues, please register domains for: {Cultures}", - publishedContent.Name, string.Join(", ", publishedCultures)); + persistedContent.Name, string.Join(", ", publishedCultures)); return; } @@ -1463,11 +1478,11 @@ private void VerifyDomainsForCultures(IContent publishedContent, PublishResult p publishResult.EventMessages.Add(new EventMessage( _localizedTextService.Localize("auditTrails", "publish"), _localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{culture}), - EventMessageType.Error + EventMessageType.Warning )); _logger.LogWarning("The root node {RootNodeName} was published in culture {Culture}, but there's no domain configured for it, this will cause routing and caching issues, please register a domain for it", - publishedContent.Name, culture); + persistedContent.Name, culture); } } From 9e5520f8c13b9885fd7411f18a4dcb2ca9934909 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Wed, 6 Oct 2021 14:49:49 +0200 Subject: [PATCH 07/32] Only warn about cultures actually being published --- .../Controllers/ContentController.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index c7fbfcaace87..78f4e6c06ebf 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -1416,7 +1416,7 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul // Verify that there's appropriate cultures configured. if (publishStatus.Success) { - VerifyDomainsForCultures(contentItem.PersistedContent, publishStatus); + VerifyDomainsForCultures(contentItem.PersistedContent, culturesToPublish, publishStatus); } return publishStatus; } @@ -1439,8 +1439,9 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul /// and there's no domain added for the published cultures /// /// + /// /// - private void VerifyDomainsForCultures(IContent persistedContent, PublishResult publishResult) + private void VerifyDomainsForCultures(IContent persistedContent, IEnumerable culturesPublished, PublishResult publishResult) { var publishedCultures = persistedContent.PublishedCultures.ToList(); // If only a single culture is published we shouldn't have any routing issues @@ -1457,8 +1458,7 @@ private void VerifyDomainsForCultures(IContent persistedContent, PublishResult p assignedDomains.AddRange(_domainService.GetAssignedDomains(ancestorID, true)); } - - // No domains at all, add a warning, and making it scary, to add domains. + // No domains at all, add a warning, to add domains. if (assignedDomains.Count == 0) { publishResult.EventMessages.Add(new EventMessage( @@ -1472,7 +1472,7 @@ private void VerifyDomainsForCultures(IContent persistedContent, PublishResult p } // If there is some domains, verify that there's a domain for each of the published cultures - foreach (var culture in publishedCultures + foreach (var culture in culturesPublished .Where(culture => assignedDomains.Any(x => x.LanguageIsoCode.Equals(culture, StringComparison.OrdinalIgnoreCase)) is false)) { publishResult.EventMessages.Add(new EventMessage( From 0f803ec7e95aded99a0805d599a3d3b3781922f3 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Thu, 7 Oct 2021 13:36:18 +0200 Subject: [PATCH 08/32] Add ContentCultureInfosCollectionBuilder test builder --- .../Builders/ContentBuilder.cs | 26 +++++++++++++ .../ContentCultureInfosCollectionBuilder.cs | 37 +++++++++++++++++++ .../IBuildContentCultureInfosCollection.cs | 7 ++++ 3 files changed, 70 insertions(+) create mode 100644 src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentCultureInfosCollection.cs diff --git a/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs index 6602c7b25ce1..452141135213 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentBuilder.cs @@ -16,6 +16,7 @@ namespace Umbraco.Cms.Tests.Common.Builders public class ContentBuilder : BuilderBase, IBuildContentTypes, + IBuildContentCultureInfosCollection, IWithIdBuilder, IWithKeyBuilder, IWithParentIdBuilder, @@ -31,6 +32,7 @@ public class ContentBuilder IWithPropertyValues { private ContentTypeBuilder _contentTypeBuilder; + private ContentCultureInfosCollectionBuilder _contentCultureInfosCollectionBuilder; private GenericDictionaryBuilder _propertyDataBuilder; private int? _id; @@ -48,6 +50,7 @@ public class ContentBuilder private bool? _trashed; private CultureInfo _cultureInfo; private IContentType _contentType; + private ContentCultureInfosCollection _contentCultureInfosCollection; private readonly IDictionary _cultureNames = new Dictionary(); private object _propertyValues; private string _propertyValuesCulture; @@ -73,6 +76,14 @@ public ContentBuilder WithContentType(IContentType contentType) return this; } + public ContentBuilder WithContentCultureInfosCollection( + ContentCultureInfosCollection contentCultureInfosCollection) + { + _contentCultureInfosCollectionBuilder = null; + _contentCultureInfosCollection = contentCultureInfosCollection; + return this; + } + public ContentBuilder WithCultureName(string culture, string name = "") { if (string.IsNullOrWhiteSpace(name)) @@ -105,6 +116,14 @@ public GenericDictionaryBuilder AddPropertyData( return builder; } + public ContentCultureInfosCollectionBuilder AddContentCultureInfosCollection() + { + _contentCultureInfosCollection = null; + var builder = new ContentCultureInfosCollectionBuilder(this); + _contentCultureInfosCollectionBuilder = builder; + return builder; + } + public override Content Build() { var id = _id ?? 0; @@ -176,6 +195,13 @@ public override Content Build() content.ResetDirtyProperties(false); } + if (_contentCultureInfosCollection is not null || _contentCultureInfosCollectionBuilder is not null) + { + ContentCultureInfosCollection contentCultureInfos = + _contentCultureInfosCollection ?? _contentCultureInfosCollectionBuilder.Build(); + content.PublishCultureInfos = contentCultureInfos; + } + return content; } diff --git a/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs new file mode 100644 index 000000000000..2d78132142e5 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Cms.Tests.Common.Builders +{ + public class ContentCultureInfosCollectionBuilder : ChildBuilderBase, IBuildContentCultureInfosCollection + { + private List _cultureInfos; + public ContentCultureInfosCollectionBuilder(ContentBuilder parentBuilder) : base(parentBuilder) => _cultureInfos = new List(); + + // TODO: Should this again wrap *another* child builder "ContentCultureInfosBuilder"? + public ContentCultureInfosCollectionBuilder WithCultureInfo(string culture, string name, DateTime? date = null) + { + if (date is null) + { + date = DateTime.Now; + } + + _cultureInfos.Add(new ContentCultureInfos(culture) { Name = name, Date = date.Value }); + return this; + } + + public override ContentCultureInfosCollection Build() + { + var cultureInfos = new ContentCultureInfosCollection(); + + foreach (ContentCultureInfos cultureInfo in _cultureInfos) + { + cultureInfos.AddOrUpdate(cultureInfo.Culture, cultureInfo.Name, cultureInfo.Date); + } + + return cultureInfos; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentCultureInfosCollection.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentCultureInfosCollection.cs new file mode 100644 index 000000000000..d47b844192f7 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IBuildContentCultureInfosCollection.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Cms.Tests.Common.Builders.Interfaces +{ + public interface IBuildContentCultureInfosCollection + { + + } +} From df1d456c982b8f00d5b1ae6a755a12f3dbfce8fc Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Thu, 7 Oct 2021 14:09:34 +0200 Subject: [PATCH 09/32] Add ContentCultureInfosBuilder --- .../Builders/ContentCultureInfosBuilder.cs | 45 +++++++++++++++++++ .../ContentCultureInfosCollectionBuilder.cs | 29 ++++++------ .../Builders/Extensions/BuilderExtensions.cs | 6 +++ .../Builders/Interfaces/IWithDateBuilder.cs | 9 ++++ 4 files changed, 74 insertions(+), 15 deletions(-) create mode 100644 src/Umbraco.Tests.Common/Builders/ContentCultureInfosBuilder.cs create mode 100644 src/Umbraco.Tests.Common/Builders/Interfaces/IWithDateBuilder.cs diff --git a/src/Umbraco.Tests.Common/Builders/ContentCultureInfosBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentCultureInfosBuilder.cs new file mode 100644 index 000000000000..ba99bcbcbd84 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/ContentCultureInfosBuilder.cs @@ -0,0 +1,45 @@ +using System; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Tests.Common.Builders.Interfaces; + +namespace Umbraco.Cms.Tests.Common.Builders +{ + public class ContentCultureInfosBuilder : ChildBuilderBase, + IWithNameBuilder, + IWithDateBuilder + { + private string _name; + private string _cultureIso; + private DateTime? _date; + public ContentCultureInfosBuilder(ContentCultureInfosCollectionBuilder parentBuilder) : base(parentBuilder) + { + } + + public ContentCultureInfosBuilder WithCultureIso(string cultureIso) + { + _cultureIso = cultureIso; + return this; + } + + public override ContentCultureInfos Build() + { + var name = _name ?? Guid.NewGuid().ToString(); + var cultureIso = _cultureIso ?? "en-us"; + DateTime date = _date ?? DateTime.Now; + + return new ContentCultureInfos(cultureIso) { Name = name, Date = date }; + } + + public string Name + { + get => _name; + set => _name = value; + } + + public DateTime? Date + { + get => _date; + set => _date = value; + } + } +} diff --git a/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs index 2d78132142e5..ba1f7e357d69 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs @@ -7,31 +7,30 @@ namespace Umbraco.Cms.Tests.Common.Builders { public class ContentCultureInfosCollectionBuilder : ChildBuilderBase, IBuildContentCultureInfosCollection { - private List _cultureInfos; - public ContentCultureInfosCollectionBuilder(ContentBuilder parentBuilder) : base(parentBuilder) => _cultureInfos = new List(); + private List _cultureInfosBuilders; + public ContentCultureInfosCollectionBuilder(ContentBuilder parentBuilder) : base(parentBuilder) => _cultureInfosBuilders = new List(); - // TODO: Should this again wrap *another* child builder "ContentCultureInfosBuilder"? - public ContentCultureInfosCollectionBuilder WithCultureInfo(string culture, string name, DateTime? date = null) + public ContentCultureInfosBuilder AddCultureInfos() { - if (date is null) - { - date = DateTime.Now; - } - - _cultureInfos.Add(new ContentCultureInfos(culture) { Name = name, Date = date.Value }); - return this; + var builder = new ContentCultureInfosBuilder(this); + _cultureInfosBuilders.Add(builder); + return builder; } public override ContentCultureInfosCollection Build() { - var cultureInfos = new ContentCultureInfosCollection(); + if (_cultureInfosBuilders.Count < 1) + { + throw new InvalidOperationException("You must add at least one culture infos to the collection builder"); + } + var cultureInfosCollection = new ContentCultureInfosCollection(); - foreach (ContentCultureInfos cultureInfo in _cultureInfos) + foreach (ContentCultureInfosBuilder cultureInfosBuilder in _cultureInfosBuilders) { - cultureInfos.AddOrUpdate(cultureInfo.Culture, cultureInfo.Name, cultureInfo.Date); + cultureInfosCollection.Add(cultureInfosBuilder.Build()); } - return cultureInfos; + return cultureInfosCollection; } } } diff --git a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs index 872a6ac367c0..0ca260b2c961 100644 --- a/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Common/Builders/Extensions/BuilderExtensions.cs @@ -234,5 +234,11 @@ public static T WithPropertyValues(this T builder, object propertyValues, str builder.PropertyValuesSegment = segment; return builder; } + + public static T WithDate(this T builder, DateTime date) where T : IWithDateBuilder + { + builder.Date = date; + return builder; + } } } diff --git a/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDateBuilder.cs b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDateBuilder.cs new file mode 100644 index 000000000000..3d1c9bddb7a8 --- /dev/null +++ b/src/Umbraco.Tests.Common/Builders/Interfaces/IWithDateBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Cms.Tests.Common.Builders.Interfaces +{ + public interface IWithDateBuilder + { + DateTime? Date { get; set; } + } +} From 11bfef4750f3fe5060f26231f5bfe6700be349f2 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Thu, 7 Oct 2021 14:12:37 +0200 Subject: [PATCH 10/32] Add unit test --- .../Controllers/ContentControllerTests.cs | 93 +++++++++++++++++++ .../Controllers/ContentController.cs | 2 +- 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs new file mode 100644 index 000000000000..d05599584ade --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -0,0 +1,93 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using NUnit.Framework; +using Umbraco.Cms.Core.Actions; +using Umbraco.Cms.Core.Dictionary; +using Umbraco.Cms.Core.Events; +using Umbraco.Cms.Core.Mapping; +using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.PropertyEditors; +using Umbraco.Cms.Core.Routing; +using Umbraco.Cms.Core.Scoping; +using Umbraco.Cms.Core.Security; +using Umbraco.Cms.Core.Serialization; +using Umbraco.Cms.Core.Services; +using Umbraco.Cms.Core.Strings; +using Umbraco.Cms.Infrastructure.Persistence; +using Umbraco.Cms.Tests.Common.Builders; +using Umbraco.Cms.Tests.Common.Builders.Extensions; +using Umbraco.Cms.Web.BackOffice.Controllers; + +namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers +{ + [TestFixture] + public class ContentControllerTests + { + [Test] + public void Root_Node_With_Domains_Causes_No_Error() + { + // Setup domain service + var domainServiceMock = new Mock(); + domainServiceMock.Setup(x => x.GetAssignedDomains(1060, It.IsAny())) + .Returns(new List{new UmbracoDomain("/", "da-dk"), new UmbracoDomain("/en", "en-us")}); + + // Create content type and content + IContentType contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build(); + + // Create content, we need to specify and ID configure domain service + Content rootNode = new ContentBuilder() + .WithContentType(contentType) + .WithId(1060) + .AddContentCultureInfosCollection() + .AddCultureInfos() + .WithCultureIso("da-dk") + .Done() + .AddCultureInfos() + .WithCultureIso("en-us") + .Done() + .Done() + .Build(); + + var culturesPublished = new List { "en-us", "da-dk" }; + var publishResult = new PublishResult(new EventMessages(), rootNode); + + ContentController contentController = CreateContentController(domainServiceMock.Object); + contentController.VerifyDomainsForCultures(rootNode, culturesPublished, publishResult); + + IEnumerable eventMessages = publishResult.EventMessages.GetAll(); + Assert.IsEmpty(eventMessages); + } + + private ContentController CreateContentController(IDomainService domainService) + { + var controller = new ContentController( + Mock.Of(), + NullLoggerFactory.Instance, + Mock.Of(), + Mock.Of(), + Mock.Of(), + new PropertyEditorCollection(new DataEditorCollection(() => null)), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + domainService, + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new ActionCollection(() => null), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of() + ); + + return controller; + } + } +} diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 78f4e6c06ebf..0d797760d53c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -1441,7 +1441,7 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul /// /// /// - private void VerifyDomainsForCultures(IContent persistedContent, IEnumerable culturesPublished, PublishResult publishResult) + internal void VerifyDomainsForCultures(IContent persistedContent, IEnumerable culturesPublished, PublishResult publishResult) { var publishedCultures = persistedContent.PublishedCultures.ToList(); // If only a single culture is published we shouldn't have any routing issues From 501ba2414f176fbb23cd39aa7ea7b9697b46d2b6 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Thu, 7 Oct 2021 15:36:11 +0200 Subject: [PATCH 11/32] Add more unit tests --- .../Controllers/ContentControllerTests.cs | 184 +++++++++++++++++- 1 file changed, 177 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index d05599584ade..9e78cfeabf18 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging.Abstractions; using Moq; @@ -26,19 +27,16 @@ namespace Umbraco.Cms.Tests.UnitTests.Umbraco.Web.BackOffice.Controllers public class ContentControllerTests { [Test] - public void Root_Node_With_Domains_Causes_No_Error() + public void Root_Node_With_Domains_Causes_No_Warning() { // Setup domain service var domainServiceMock = new Mock(); domainServiceMock.Setup(x => x.GetAssignedDomains(1060, It.IsAny())) - .Returns(new List{new UmbracoDomain("/", "da-dk"), new UmbracoDomain("/en", "en-us")}); + .Returns(new []{new UmbracoDomain("/", "da-dk"), new UmbracoDomain("/en", "en-us")}); - // Create content type and content - IContentType contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build(); - - // Create content, we need to specify and ID configure domain service + // Create content, we need to specify and ID in order to be able to configure domain service Content rootNode = new ContentBuilder() - .WithContentType(contentType) + .WithContentType(CreateContentType()) .WithId(1060) .AddContentCultureInfosCollection() .AddCultureInfos() @@ -60,6 +58,175 @@ public void Root_Node_With_Domains_Causes_No_Error() Assert.IsEmpty(eventMessages); } + [Test] + public void Node_With_Single_Published_Culture_Causes_No_Warning() + { + var domainServiceMock = new Mock(); + domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny(), It.IsAny())) + .Returns(Enumerable.Empty()); + + Content rootNode = new ContentBuilder() + .WithContentType(CreateContentType()) + .WithId(1060) + .AddContentCultureInfosCollection() + .AddCultureInfos() + .WithCultureIso("da-dk") + .Done() + .Done() + .Build(); + + var culturesPublished = new List {"da-dk" }; + var publishResult = new PublishResult(new EventMessages(), rootNode); + + ContentController contentController = CreateContentController(domainServiceMock.Object); + contentController.VerifyDomainsForCultures(rootNode, culturesPublished, publishResult); + + Assert.IsEmpty(publishResult.EventMessages.GetAll()); + } + + [Test] + public void Root_Node_Without_Domains_Causes_SingleWarning() + { + var domainServiceMock = new Mock(); + domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny(), It.IsAny())) + .Returns(Enumerable.Empty()); + + Content rootNode = new ContentBuilder() + .WithContentType(CreateContentType()) + .WithId(1060) + .AddContentCultureInfosCollection() + .AddCultureInfos() + .WithCultureIso("da-dk") + .Done() + .AddCultureInfos() + .WithCultureIso("en-us") + .Done() + .Done() + .Build(); + + var culturesPublished = new List { "en-us", "da-dk" }; + var publishResult = new PublishResult(new EventMessages(), rootNode); + + ContentController contentController = CreateContentController(domainServiceMock.Object); + contentController.VerifyDomainsForCultures(rootNode, culturesPublished, publishResult); + Assert.AreEqual(1, publishResult.EventMessages.Count); + } + + [Test] + public void One_Warning_Per_Culture_Being_Published() + { + var domainServiceMock = new Mock(); + domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny(), It.IsAny())) + .Returns(new []{new UmbracoDomain("/", "da-dk")}); + + + Content rootNode = new ContentBuilder() + .WithContentType(CreateContentType()) + .WithId(1060) + .AddContentCultureInfosCollection() + .AddCultureInfos() + .WithCultureIso("da-dk") + .Done() + .AddCultureInfos() + .WithCultureIso("en-us") + .Done() + .Done() + .Build(); + + var culturesPublished = new List { "en-us", "da-dk", "nl-bk", "se-sv" }; + var publishResult = new PublishResult(new EventMessages(), rootNode); + + ContentController contentController = CreateContentController(domainServiceMock.Object); + contentController.VerifyDomainsForCultures(rootNode, culturesPublished, publishResult); + Assert.AreEqual(3, publishResult.EventMessages.Count); + } + + [Test] + public void Ancestor_Domains_Counts() + { + var rootId = 1060; + var level1Id = 1061; + var level2Id = 1062; + var level3Id = 1063; + + var domainServiceMock = new Mock(); + domainServiceMock.Setup(x => x.GetAssignedDomains(rootId, It.IsAny())) + .Returns(new[] { new UmbracoDomain("/", "da-dk") }); + + domainServiceMock.Setup(x => x.GetAssignedDomains(level1Id, It.IsAny())) + .Returns(new[] { new UmbracoDomain("/en", "en-us") }); + + domainServiceMock.Setup(x => x.GetAssignedDomains(level2Id, It.IsAny())) + .Returns(new[] { new UmbracoDomain("/se", "se-sv"), new UmbracoDomain("/nl", "nl-bk") }); + + Content level3Node = new ContentBuilder() + .WithContentType(CreateContentType()) + .WithId(level3Id) + .WithPath($"-1,{rootId},{level1Id},{level2Id},{level3Id}") + .AddContentCultureInfosCollection() + .AddCultureInfos() + .WithCultureIso("da-dk") + .Done() + .AddCultureInfos() + .WithCultureIso("en-us") + .Done() + .AddCultureInfos() + .WithCultureIso("se-sv") + .Done() + .AddCultureInfos() + .WithCultureIso("nl-bk") + .Done() + .AddCultureInfos() + .WithCultureIso("de-de") + .Done() + .Done() + .Build(); + + var culturesPublished = new List { "en-us", "da-dk", "nl-bk", "se-sv", "de-de" }; + var publishResult = new PublishResult(new EventMessages(), level3Node); + + ContentController contentController = CreateContentController(domainServiceMock.Object); + contentController.VerifyDomainsForCultures(level3Node, culturesPublished, publishResult); + // We expect one error because all domains except "de-de" is registered somewhere in the ancestor path + Assert.AreEqual(1, publishResult.EventMessages.Count); + } + + [Test] + public void Only_Warns_About_Cultures_Being_Published() + { + var domainServiceMock = new Mock(); + domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny(), It.IsAny())) + .Returns(new []{new UmbracoDomain("/", "da-dk")}); + + Content rootNode = new ContentBuilder() + .WithContentType(CreateContentType()) + .WithId(1060) + .AddContentCultureInfosCollection() + .AddCultureInfos() + .WithCultureIso("da-dk") + .Done() + .AddCultureInfos() + .WithCultureIso("en-us") + .Done() + .AddCultureInfos() + .WithCultureIso("se-sv") + .Done() + .AddCultureInfos() + .WithCultureIso("de-de") + .Done() + .Done() + .Build(); + + var culturesPublished = new List { "en-us", "se-sv" }; + var publishResult = new PublishResult(new EventMessages(), rootNode); + + ContentController contentController = CreateContentController(domainServiceMock.Object); + contentController.VerifyDomainsForCultures(rootNode, culturesPublished, publishResult); + + // We only get two errors, one for each culture being published, so no errors from previously published cultures. + Assert.AreEqual(2, publishResult.EventMessages.Count); + } + private ContentController CreateContentController(IDomainService domainService) { var controller = new ContentController( @@ -89,5 +256,8 @@ private ContentController CreateContentController(IDomainService domainService) return controller; } + + private IContentType CreateContentType() => + new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build(); } } From f0f8874727955ac25d2a9687ed9a931ce263d902 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Thu, 7 Oct 2021 16:34:38 +0200 Subject: [PATCH 12/32] Add integration test --- .../Controllers/ContentControllerTests.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index ab2acf982502..486a5c094d5a 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -406,5 +406,41 @@ public async Task PostSave_Validate_Variants_Empty_Name() CollectionAssert.Contains(display.Errors.Keys, "_content_variant_en-US_null_"); }); } + + [Test] + public async Task PostSave_Validates_Domains() + { + ILocalizationService localizationService = GetRequiredService(); + localizationService.Save(new LanguageBuilder() + .WithCultureInfo("da-DK") + .WithIsDefault(false) + .Build()); + + IContentTypeService contentTypeService = GetRequiredService(); + var contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build(); + contentTypeService.Save(contentType); + + var content = new ContentBuilder() + .WithId(1) + .WithContentType(contentType) + .WithCultureName("en-US", "Root") + .WithCultureName("da-DK", "Rod") + .Build(); + + var model = new ContentItemSaveBuilder() + .WithContent(content) + .WithAction(ContentSaveAction.PublishNew) + .Build(); + + string url = PrepareApiControllerUrl(x => x.PostSave(null)); + + HttpResponseMessage response = await Client.PostAsync(url, new MultipartFormDataContent + { + { new StringContent(JsonConvert.SerializeObject(model)), "contentItem" } + }); + string body = await response.Content.ReadAsStringAsync(); + body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + ContentItemDisplay display = JsonConvert.DeserializeObject(body); + } } } From 8e7fb897d107ab4ebd55ddf65650a7fe67d4f069 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Fri, 8 Oct 2021 11:24:10 +0200 Subject: [PATCH 13/32] Use proper notifications instead of event messages --- .../Controllers/ContentControllerTests.cs | 2 + .../Controllers/ContentControllerTests.cs | 52 +++++++++++-------- .../Controllers/ContentController.cs | 32 ++++++------ 3 files changed, 49 insertions(+), 37 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 486a5c094d5a..59c9b04dd064 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -441,6 +441,8 @@ public async Task PostSave_Validates_Domains() string body = await response.Content.ReadAsStringAsync(); body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); + Assert.IsNotNull(display); + Assert.AreEqual(1, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); } } } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 9e78cfeabf18..7e8c2c83a54e 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -1,4 +1,6 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Globalization; using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging.Abstractions; @@ -9,6 +11,7 @@ using Umbraco.Cms.Core.Events; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; +using Umbraco.Cms.Core.Models.ContentEditing; using Umbraco.Cms.Core.PropertyEditors; using Umbraco.Cms.Core.Routing; using Umbraco.Cms.Core.Scoping; @@ -49,13 +52,12 @@ public void Root_Node_With_Domains_Causes_No_Warning() .Build(); var culturesPublished = new List { "en-us", "da-dk" }; - var publishResult = new PublishResult(new EventMessages(), rootNode); + var notifications = new SimpleNotificationModel(); ContentController contentController = CreateContentController(domainServiceMock.Object); - contentController.VerifyDomainsForCultures(rootNode, culturesPublished, publishResult); + contentController.AddDomainWarnings(rootNode, culturesPublished, notifications); - IEnumerable eventMessages = publishResult.EventMessages.GetAll(); - Assert.IsEmpty(eventMessages); + Assert.IsEmpty(notifications.Notifications); } [Test] @@ -76,12 +78,12 @@ public void Node_With_Single_Published_Culture_Causes_No_Warning() .Build(); var culturesPublished = new List {"da-dk" }; - var publishResult = new PublishResult(new EventMessages(), rootNode); + var notifications = new SimpleNotificationModel(); ContentController contentController = CreateContentController(domainServiceMock.Object); - contentController.VerifyDomainsForCultures(rootNode, culturesPublished, publishResult); + contentController.AddDomainWarnings(rootNode, culturesPublished, notifications); - Assert.IsEmpty(publishResult.EventMessages.GetAll()); + Assert.IsEmpty(notifications.Notifications); } [Test] @@ -105,11 +107,11 @@ public void Root_Node_Without_Domains_Causes_SingleWarning() .Build(); var culturesPublished = new List { "en-us", "da-dk" }; - var publishResult = new PublishResult(new EventMessages(), rootNode); + var notifications = new SimpleNotificationModel(); ContentController contentController = CreateContentController(domainServiceMock.Object); - contentController.VerifyDomainsForCultures(rootNode, culturesPublished, publishResult); - Assert.AreEqual(1, publishResult.EventMessages.Count); + contentController.AddDomainWarnings(rootNode, culturesPublished, notifications); + Assert.AreEqual(1, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); } [Test] @@ -134,11 +136,11 @@ public void One_Warning_Per_Culture_Being_Published() .Build(); var culturesPublished = new List { "en-us", "da-dk", "nl-bk", "se-sv" }; - var publishResult = new PublishResult(new EventMessages(), rootNode); + var notifications = new SimpleNotificationModel(); ContentController contentController = CreateContentController(domainServiceMock.Object); - contentController.VerifyDomainsForCultures(rootNode, culturesPublished, publishResult); - Assert.AreEqual(3, publishResult.EventMessages.Count); + contentController.AddDomainWarnings(rootNode, culturesPublished, notifications); + Assert.AreEqual(3, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); } [Test] @@ -183,12 +185,13 @@ public void Ancestor_Domains_Counts() .Build(); var culturesPublished = new List { "en-us", "da-dk", "nl-bk", "se-sv", "de-de" }; - var publishResult = new PublishResult(new EventMessages(), level3Node); ContentController contentController = CreateContentController(domainServiceMock.Object); - contentController.VerifyDomainsForCultures(level3Node, culturesPublished, publishResult); + var notifications = new SimpleNotificationModel(); + + contentController.AddDomainWarnings(level3Node, culturesPublished, notifications); // We expect one error because all domains except "de-de" is registered somewhere in the ancestor path - Assert.AreEqual(1, publishResult.EventMessages.Count); + Assert.AreEqual(1, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); } [Test] @@ -218,23 +221,30 @@ public void Only_Warns_About_Cultures_Being_Published() .Build(); var culturesPublished = new List { "en-us", "se-sv" }; - var publishResult = new PublishResult(new EventMessages(), rootNode); + var notifications = new SimpleNotificationModel(); ContentController contentController = CreateContentController(domainServiceMock.Object); - contentController.VerifyDomainsForCultures(rootNode, culturesPublished, publishResult); + contentController.AddDomainWarnings(rootNode, culturesPublished, notifications); // We only get two errors, one for each culture being published, so no errors from previously published cultures. - Assert.AreEqual(2, publishResult.EventMessages.Count); + Assert.AreEqual(2, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); } private ContentController CreateContentController(IDomainService domainService) { + // We have to configure ILocalizedTextService to return a new string every time Localize is called + // Otherwise it won't add the notification because it skips dupes + var localizedTextServiceMock = new Mock(); + localizedTextServiceMock.Setup(x => x.Localize(It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>())) + .Returns(() => Guid.NewGuid().ToString()); + var controller = new ContentController( Mock.Of(), NullLoggerFactory.Instance, Mock.Of(), Mock.Of(), - Mock.Of(), + localizedTextServiceMock.Object, new PropertyEditorCollection(new DataEditorCollection(() => null)), Mock.Of(), Mock.Of(), diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 0d797760d53c..f67a65dde347 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -879,7 +879,9 @@ private async Task> PostSaveInternal(ContentIte case ContentSaveAction.Publish: case ContentSaveAction.PublishNew: { - var publishStatus = PublishInternal(contentItem, defaultCulture, cultureForInvariantErrors, out wasCancelled, out var successfulCultures); + PublishResult publishStatus = PublishInternal(contentItem, defaultCulture, cultureForInvariantErrors, out wasCancelled, out var successfulCultures); + // Add warnings if domains are not set up correctly + AddDomainWarnings(publishStatus.Content, successfulCultures, globalNotifications); AddPublishStatusNotifications(new[] { publishStatus }, globalNotifications, notifications, successfulCultures); } break; @@ -1413,11 +1415,6 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul wasCancelled = publishStatus.Result == PublishResultType.FailedPublishCancelledByEvent; successfulCultures = culturesToPublish; - // Verify that there's appropriate cultures configured. - if (publishStatus.Success) - { - VerifyDomainsForCultures(contentItem.PersistedContent, culturesToPublish, publishStatus); - } return publishStatus; } else @@ -1435,14 +1432,20 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul /// Verifies that there's an appropriate domain setup for the published cultures /// /// - /// Adds a warning and logs a message if, a node varies but culture, there's at least 1 culture already published, + /// Adds a warning and logs a message if a node varies by culture, there's at least 1 culture already published, /// and there's no domain added for the published cultures /// /// /// - /// - internal void VerifyDomainsForCultures(IContent persistedContent, IEnumerable culturesPublished, PublishResult publishResult) + /// + internal void AddDomainWarnings(IContent persistedContent, IEnumerable culturesPublished, SimpleNotificationModel globalNotifications) { + // Don't try to verify if no cultures were published + if (culturesPublished is null) + { + return; + } + var publishedCultures = persistedContent.PublishedCultures.ToList(); // If only a single culture is published we shouldn't have any routing issues if (publishedCultures.Count < 2) @@ -1461,10 +1464,9 @@ internal void VerifyDomainsForCultures(IContent persistedContent, IEnumerable assignedDomains.Any(x => x.LanguageIsoCode.Equals(culture, StringComparison.OrdinalIgnoreCase)) is false)) { - publishResult.EventMessages.Add(new EventMessage( + globalNotifications.AddWarningNotification( _localizedTextService.Localize("auditTrails", "publish"), - _localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{culture}), - EventMessageType.Warning - )); + _localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{culture})); _logger.LogWarning("The root node {RootNodeName} was published in culture {Culture}, but there's no domain configured for it, this will cause routing and caching issues, please register a domain for it", persistedContent.Name, culture); From 5a82d8ee2ea401e58a38db69c97756afd4ec609f Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Fri, 8 Oct 2021 11:35:56 +0200 Subject: [PATCH 14/32] Also display warning when publishing with descendants --- .../Controllers/ContentControllerTests.cs | 12 ++++++------ .../Controllers/ContentController.cs | 12 +++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 7e8c2c83a54e..42b9eb2ddc8d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -51,7 +51,7 @@ public void Root_Node_With_Domains_Causes_No_Warning() .Done() .Build(); - var culturesPublished = new List { "en-us", "da-dk" }; + var culturesPublished = new []{ "en-us", "da-dk" }; var notifications = new SimpleNotificationModel(); ContentController contentController = CreateContentController(domainServiceMock.Object); @@ -77,7 +77,7 @@ public void Node_With_Single_Published_Culture_Causes_No_Warning() .Done() .Build(); - var culturesPublished = new List {"da-dk" }; + var culturesPublished = new []{"da-dk" }; var notifications = new SimpleNotificationModel(); ContentController contentController = CreateContentController(domainServiceMock.Object); @@ -106,7 +106,7 @@ public void Root_Node_Without_Domains_Causes_SingleWarning() .Done() .Build(); - var culturesPublished = new List { "en-us", "da-dk" }; + var culturesPublished = new []{ "en-us", "da-dk" }; var notifications = new SimpleNotificationModel(); ContentController contentController = CreateContentController(domainServiceMock.Object); @@ -135,7 +135,7 @@ public void One_Warning_Per_Culture_Being_Published() .Done() .Build(); - var culturesPublished = new List { "en-us", "da-dk", "nl-bk", "se-sv" }; + var culturesPublished = new []{ "en-us", "da-dk", "nl-bk", "se-sv" }; var notifications = new SimpleNotificationModel(); ContentController contentController = CreateContentController(domainServiceMock.Object); @@ -184,7 +184,7 @@ public void Ancestor_Domains_Counts() .Done() .Build(); - var culturesPublished = new List { "en-us", "da-dk", "nl-bk", "se-sv", "de-de" }; + var culturesPublished = new []{ "en-us", "da-dk", "nl-bk", "se-sv", "de-de" }; ContentController contentController = CreateContentController(domainServiceMock.Object); var notifications = new SimpleNotificationModel(); @@ -220,7 +220,7 @@ public void Only_Warns_About_Cultures_Being_Published() .Done() .Build(); - var culturesPublished = new List { "en-us", "se-sv" }; + var culturesPublished = new []{ "en-us", "se-sv" }; var notifications = new SimpleNotificationModel(); ContentController contentController = CreateContentController(domainServiceMock.Object); diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index f67a65dde347..3fe3b784cfe7 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -898,6 +898,7 @@ private async Task> PostSaveInternal(ContentIte } var publishStatus = PublishBranchInternal(contentItem, false, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList(); + AddDomainWarnings(publishStatus, successfulCultures, globalNotifications); AddPublishStatusNotifications(publishStatus, globalNotifications, notifications, successfulCultures); } break; @@ -1428,6 +1429,15 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul } } + private void AddDomainWarnings(IEnumerable publishResults, string[] culturesPublished, + SimpleNotificationModel globalNotifications) + { + foreach (PublishResult publishResult in publishResults) + { + AddDomainWarnings(publishResult.Content, culturesPublished, globalNotifications); + } + } + /// /// Verifies that there's an appropriate domain setup for the published cultures /// @@ -1438,7 +1448,7 @@ private PublishResult PublishInternal(ContentItemSave contentItem, string defaul /// /// /// - internal void AddDomainWarnings(IContent persistedContent, IEnumerable culturesPublished, SimpleNotificationModel globalNotifications) + internal void AddDomainWarnings(IContent persistedContent, string[] culturesPublished, SimpleNotificationModel globalNotifications) { // Don't try to verify if no cultures were published if (culturesPublished is null) From 8112e1a9af7b0e4fb05c7e231d11401a75a075ec Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Fri, 8 Oct 2021 13:08:32 +0200 Subject: [PATCH 15/32] Add intergration test --- .../Controllers/ContentControllerTests.cs | 85 +++++++++++++++++-- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 59c9b04dd064..aac0f6b41618 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -408,7 +408,7 @@ public async Task PostSave_Validate_Variants_Empty_Name() } [Test] - public async Task PostSave_Validates_Domains() + public async Task PostSave_Validates_Domains_Exist() { ILocalizationService localizationService = GetRequiredService(); localizationService.Save(new LanguageBuilder() @@ -417,32 +417,101 @@ public async Task PostSave_Validates_Domains() .Build()); IContentTypeService contentTypeService = GetRequiredService(); - var contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build(); + IContentType contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build(); contentTypeService.Save(contentType); - var content = new ContentBuilder() + Content content = new ContentBuilder() .WithId(1) .WithContentType(contentType) .WithCultureName("en-US", "Root") .WithCultureName("da-DK", "Rod") .Build(); - var model = new ContentItemSaveBuilder() + ContentItemSave model = new ContentItemSaveBuilder() .WithContent(content) .WithAction(ContentSaveAction.PublishNew) .Build(); - string url = PrepareApiControllerUrl(x => x.PostSave(null)); + var url = PrepareApiControllerUrl(x => x.PostSave(null)); HttpResponseMessage response = await Client.PostAsync(url, new MultipartFormDataContent { { new StringContent(JsonConvert.SerializeObject(model)), "contentItem" } }); - string body = await response.Content.ReadAsStringAsync(); + var body = await response.Content.ReadAsStringAsync(); + body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + ContentItemDisplay display = JsonConvert.DeserializeObject(body); + + ILocalizedTextService localizedTextService = GetRequiredService(); + var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithNoDomains"); + + Assert.Multiple(() => + { + Assert.IsNotNull(display); + Assert.AreEqual(1, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); + Assert.AreEqual(expectedMessage, display.Notifications.First().Message); + }); + } + + [Test] + public async Task PostSave_Validates_All_Cultures_Has_Domains() + { + var enString = "en-US"; + var dkString = "da-DK"; + + ILocalizationService localizationService = GetRequiredService(); + localizationService.Save(new LanguageBuilder() + .WithCultureInfo(dkString) + .WithIsDefault(false) + .Build()); + + IContentTypeService contentTypeService = GetRequiredService(); + IContentType contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build(); + contentTypeService.Save(contentType); + + Content content = new ContentBuilder() + .WithoutIdentity() + .WithContentType(contentType) + .WithCultureName(enString, "Root") + .WithCultureName(dkString, "Rod") + .Build(); + + IContentService contentService = GetRequiredService(); + contentService.Save(content); + + ContentItemSave model = new ContentItemSaveBuilder() + .WithContent(content) + .WithAction(ContentSaveAction.Publish) + .Build(); + + ILanguage dkLanguage = localizationService.GetLanguageByIsoCode(dkString); + IDomainService domainService = GetRequiredService(); + var dkDomain = new UmbracoDomain("/") + { + RootContentId = content.Id, + LanguageId = dkLanguage.Id + }; + domainService.Save(dkDomain); + + var url = PrepareApiControllerUrl(x => x.PostSave(null)); + + HttpResponseMessage response = await Client.PostAsync(url, new MultipartFormDataContent + { + { new StringContent(JsonConvert.SerializeObject(model)), "contentItem" } + }); + + var body = await response.Content.ReadAsStringAsync(); body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); - Assert.IsNotNull(display); - Assert.AreEqual(1, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); + + ILocalizedTextService localizedTextService = GetRequiredService(); + var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{enString}); + Assert.Multiple(() => + { + Assert.NotNull(display); + Assert.AreEqual(1, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); + Assert.AreEqual(expectedMessage, display.Notifications.FirstOrDefault(x => x.NotificationType == NotificationStyle.Warning)?.Message); + }); } } } From 597b6f4bd28625552929aa0252559876b7de3ea9 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Fri, 8 Oct 2021 14:52:22 +0200 Subject: [PATCH 16/32] Add final integration test --- .../Controllers/ContentControllerTests.cs | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index aac0f6b41618..2de19c799e48 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -513,5 +513,94 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() Assert.AreEqual(expectedMessage, display.Notifications.FirstOrDefault(x => x.NotificationType == NotificationStyle.Warning)?.Message); }); } + + [Test] + public async Task PostSave_Checks_Ancestors_For_Domains() + { + var enString = "en-US"; + var dkString = "da-DK"; + + ILocalizationService localizationService = GetRequiredService(); + localizationService.Save(new LanguageBuilder() + .WithCultureInfo(dkString) + .WithIsDefault(false) + .Build()); + + IContentTypeService contentTypeService = GetRequiredService(); + IContentType contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build(); + contentTypeService.Save(contentType); + + Content rootNode = new ContentBuilder() + .WithoutIdentity() + .WithContentType(contentType) + .WithCultureName(enString, "Root") + .WithCultureName(dkString, "Rod") + .Build(); + + IContentService contentService = GetRequiredService(); + contentService.SaveAndPublish(rootNode); + + Content childNode = new ContentBuilder() + .WithoutIdentity() + .WithParent(rootNode) + .WithContentType(contentType) + .WithCultureName(dkString, "Barn") + .WithCultureName(enString, "Child") + .Build(); + + contentService.SaveAndPublish(childNode); + + Content grandChild = new ContentBuilder() + .WithoutIdentity() + .WithParent(childNode) + .WithContentType(contentType) + .WithCultureName(dkString, "BarneBarn") + .WithCultureName(enString, "GrandChild") + .Build(); + + contentService.Save(grandChild); + + ILanguage dkLanguage = localizationService.GetLanguageByIsoCode(dkString); + ILanguage usLanguage = localizationService.GetLanguageByIsoCode(enString); + IDomainService domainService = GetRequiredService(); + var dkDomain = new UmbracoDomain("/") + { + RootContentId = rootNode.Id, + LanguageId = dkLanguage.Id + }; + + var usDomain = new UmbracoDomain("/en") + { + RootContentId = childNode.Id, + LanguageId = usLanguage.Id + }; + + domainService.Save(dkDomain); + domainService.Save(usDomain); + + var url = PrepareApiControllerUrl(x => x.PostSave(null)); + + ContentItemSave model = new ContentItemSaveBuilder() + .WithContent(grandChild) + .WithAction(ContentSaveAction.Publish) + .Build(); + + HttpResponseMessage response = await Client.PostAsync(url, new MultipartFormDataContent + { + { new StringContent(JsonConvert.SerializeObject(model)), "contentItem" } + }); + + var body = await response.Content.ReadAsStringAsync(); + body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + ContentItemDisplay display = JsonConvert.DeserializeObject(body); + + Assert.Multiple(() => + { + Assert.NotNull(display); + // Assert all is good, a success notification for each culture published and no warnings. + Assert.AreEqual(2, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Success)); + Assert.AreEqual(0, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); + }); + } } } From 161fc7d45634189af9e137d76b3abdc30087bd87 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Fri, 8 Oct 2021 14:55:44 +0200 Subject: [PATCH 17/32] Use hashset instead of list for assigned domains We don't care about dupes --- src/Umbraco.Web.BackOffice/Controllers/ContentController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 3fe3b784cfe7..3f051d0a3587 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -1464,11 +1464,11 @@ internal void AddDomainWarnings(IContent persistedContent, string[] culturesPubl } // If more than a single culture is published we need to verify that there's a domain registered for each published culture - var assignedDomains = _domainService.GetAssignedDomains(persistedContent.Id, true).ToList(); + var assignedDomains = _domainService.GetAssignedDomains(persistedContent.Id, true).ToHashSet(); // We also have to check all of the ancestors, if any of those has the appropriate culture assigned we don't need to warn foreach (var ancestorID in persistedContent.GetAncestorIds()) { - assignedDomains.AddRange(_domainService.GetAssignedDomains(ancestorID, true)); + assignedDomains.UnionWith(_domainService.GetAssignedDomains(ancestorID, true)); } // No domains at all, add a warning, to add domains. From 7c2f5c934cac33d886525b13d49ab58cbebd1480 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Tue, 12 Oct 2021 12:53:07 +0200 Subject: [PATCH 18/32] Add EN translations --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index dbb8a7a6b6ea..884aa7b68289 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1441,6 +1441,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Invitation has been re-sent to %0% Document Type was exported to file An error occurred while exporting the Document Type + Domains are not configured for multilingual site, please contact an administrator, see log for more information + There is no domain configured for %0%, please contact an administrator, see log for more information Add style From 6fcef7bc339fdf30aa12dce2b256d072aabca97c Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 12 Oct 2021 14:05:45 +0200 Subject: [PATCH 19/32] Ensure current thread culture --- .../Controllers/ContentControllerTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 2de19c799e48..9ebed26571af 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -1,9 +1,11 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using NUnit.Framework; @@ -410,6 +412,7 @@ public async Task PostSave_Validate_Variants_Empty_Name() [Test] public async Task PostSave_Validates_Domains_Exist() { + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); ILocalizationService localizationService = GetRequiredService(); localizationService.Save(new LanguageBuilder() .WithCultureInfo("da-DK") @@ -506,6 +509,7 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() ILocalizedTextService localizedTextService = GetRequiredService(); var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{enString}); + Assert.Multiple(() => { Assert.NotNull(display); From ab0ac0f5997b7b43c432ac33b3a9a17158f7fef7 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 12 Oct 2021 14:05:45 +0200 Subject: [PATCH 20/32] Ensure current thread culture --- .../Controllers/ContentControllerTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 2de19c799e48..6cbc036ec9de 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -1,9 +1,11 @@ // Copyright (c) Umbraco. // See LICENSE for more details. +using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using NUnit.Framework; @@ -410,6 +412,7 @@ public async Task PostSave_Validate_Variants_Empty_Name() [Test] public async Task PostSave_Validates_Domains_Exist() { + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); ILocalizationService localizationService = GetRequiredService(); localizationService.Save(new LanguageBuilder() .WithCultureInfo("da-DK") @@ -456,6 +459,7 @@ public async Task PostSave_Validates_Domains_Exist() [Test] public async Task PostSave_Validates_All_Cultures_Has_Domains() { + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); var enString = "en-US"; var dkString = "da-DK"; @@ -506,6 +510,7 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() ILocalizedTextService localizedTextService = GetRequiredService(); var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{enString}); + Assert.Multiple(() => { Assert.NotNull(display); From e64d1ff4d17590bbb0b900713cfef8c30ef62ed6 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 14 Oct 2021 11:26:33 +0200 Subject: [PATCH 21/32] Removed check for message --- .../Controllers/ContentControllerTests.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 6cbc036ec9de..31761923d0f1 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -445,14 +445,10 @@ public async Task PostSave_Validates_Domains_Exist() body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); - ILocalizedTextService localizedTextService = GetRequiredService(); - var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithNoDomains"); - Assert.Multiple(() => { Assert.IsNotNull(display); Assert.AreEqual(1, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); - Assert.AreEqual(expectedMessage, display.Notifications.First().Message); }); } @@ -508,14 +504,11 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); - ILocalizedTextService localizedTextService = GetRequiredService(); - var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{enString}); Assert.Multiple(() => { Assert.NotNull(display); Assert.AreEqual(1, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); - Assert.AreEqual(expectedMessage, display.Notifications.FirstOrDefault(x => x.NotificationType == NotificationStyle.Warning)?.Message); }); } From 7379387b2eb2b795f70ba360caf8a063aeb442fe Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 14 Oct 2021 13:30:22 +0200 Subject: [PATCH 22/32] Added logging --- .../Controllers/ContentControllerTests.cs | 10 ++++++++-- .../Controllers/ContentControllerTests.cs | 3 ++- .../Controllers/ContentController.cs | 12 +++++++++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 31761923d0f1..d913d62372b9 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -412,7 +412,6 @@ public async Task PostSave_Validate_Variants_Empty_Name() [Test] public async Task PostSave_Validates_Domains_Exist() { - Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); ILocalizationService localizationService = GetRequiredService(); localizationService.Save(new LanguageBuilder() .WithCultureInfo("da-DK") @@ -445,17 +444,21 @@ public async Task PostSave_Validates_Domains_Exist() body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); + Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + ILocalizedTextService localizedTextService = GetRequiredService(); + var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithNoDomains", new []{"en-US"}); + Assert.Multiple(() => { Assert.IsNotNull(display); Assert.AreEqual(1, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); + Assert.AreEqual(expectedMessage, display.Notifications.FirstOrDefault(x => x.NotificationType == NotificationStyle.Warning)?.Message); }); } [Test] public async Task PostSave_Validates_All_Cultures_Has_Domains() { - Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); var enString = "en-US"; var dkString = "da-DK"; @@ -504,11 +507,14 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); + ILocalizedTextService localizedTextService = GetRequiredService(); + var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{"en-US"}); Assert.Multiple(() => { Assert.NotNull(display); Assert.AreEqual(1, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); + Assert.AreEqual(expectedMessage, display.Notifications.FirstOrDefault(x => x.NotificationType == NotificationStyle.Warning)?.Message); }); } diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 42b9eb2ddc8d..24a943265e20 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -261,7 +261,8 @@ private ContentController CreateContentController(IDomainService domainService) Mock.Of(), Mock.Of(), Mock.Of(), - Mock.Of() + Mock.Of(), + Mock.Of() ); return controller; diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 3f051d0a3587..a05aa25238dd 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -65,6 +65,7 @@ public class ContentController : ContentControllerBase private readonly Lazy> _allLangs; private readonly ILogger _logger; private readonly IScopeProvider _scopeProvider; + private readonly IUserDataService _userDataService; public object Domains { get; private set; } @@ -90,7 +91,8 @@ public ContentController( ISqlContext sqlContext, IJsonSerializer serializer, IScopeProvider scopeProvider, - IAuthorizationService authorizationService) + IAuthorizationService authorizationService, + IUserDataService userDataService) : base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, serializer) { _propertyEditors = propertyEditors; @@ -112,6 +114,7 @@ public ContentController( _logger = loggerFactory.CreateLogger(); _scopeProvider = scopeProvider; _allLangs = new Lazy>(() => _localizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase)); + _userDataService = userDataService; } /// @@ -1470,14 +1473,17 @@ internal void AddDomainWarnings(IContent persistedContent, string[] culturesPubl { assignedDomains.UnionWith(_domainService.GetAssignedDomains(ancestorID, true)); } - + foreach (var data in _userDataService.GetUserData()) + { + _logger.LogWarning($"{data.Name} : {data.Data}"); + } // No domains at all, add a warning, to add domains. if (assignedDomains.Count == 0) { globalNotifications.AddWarningNotification( _localizedTextService.Localize("auditTrails", "publish"), _localizedTextService.Localize("speechBubbles", "publishWithNoDomains")); - + _logger.LogWarning("NOT REGISTRED DOMAIN FOR: {Cultures}"); _logger.LogWarning("The root node {RootNodeName} was published with multiple cultures, but no domains are configured, this will cause routing and caching issues, please register domains for: {Cultures}", persistedContent.Name, string.Join(", ", publishedCultures)); return; From 76a8d4c474eddc6c0f2ea3ee9fd6687cf74c06bd Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 14 Oct 2021 14:07:31 +0200 Subject: [PATCH 23/32] Added logging in test --- .../Controllers/ContentControllerTests.cs | 7 ++++++- .../Controllers/ContentControllerTests.cs | 3 +-- .../Controllers/ContentController.cs | 12 +++--------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index d913d62372b9..540aa9172523 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -444,8 +444,12 @@ public async Task PostSave_Validates_Domains_Exist() body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); - Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US"); + TestContext.Progress.Write($"======CURRENT THREAD CULTURE IN TEST======{Thread.CurrentThread.CurrentCulture.Name}"); ILocalizedTextService localizedTextService = GetRequiredService(); + foreach (var culture in localizedTextService.GetSupportedCultures()) + { + TestContext.Progress.Write($"======SUPPORTED CULTURES IN TEXT SERVICE======{culture.Name}"); + } var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithNoDomains", new []{"en-US"}); Assert.Multiple(() => @@ -507,6 +511,7 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); + ILocalizedTextService localizedTextService = GetRequiredService(); var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{"en-US"}); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 24a943265e20..42b9eb2ddc8d 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -261,8 +261,7 @@ private ContentController CreateContentController(IDomainService domainService) Mock.Of(), Mock.Of(), Mock.Of(), - Mock.Of(), - Mock.Of() + Mock.Of() ); return controller; diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index a05aa25238dd..3f051d0a3587 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -65,7 +65,6 @@ public class ContentController : ContentControllerBase private readonly Lazy> _allLangs; private readonly ILogger _logger; private readonly IScopeProvider _scopeProvider; - private readonly IUserDataService _userDataService; public object Domains { get; private set; } @@ -91,8 +90,7 @@ public ContentController( ISqlContext sqlContext, IJsonSerializer serializer, IScopeProvider scopeProvider, - IAuthorizationService authorizationService, - IUserDataService userDataService) + IAuthorizationService authorizationService) : base(cultureDictionary, loggerFactory, shortStringHelper, eventMessages, localizedTextService, serializer) { _propertyEditors = propertyEditors; @@ -114,7 +112,6 @@ public ContentController( _logger = loggerFactory.CreateLogger(); _scopeProvider = scopeProvider; _allLangs = new Lazy>(() => _localizationService.GetAllLanguages().ToDictionary(x => x.IsoCode, x => x, StringComparer.InvariantCultureIgnoreCase)); - _userDataService = userDataService; } /// @@ -1473,17 +1470,14 @@ internal void AddDomainWarnings(IContent persistedContent, string[] culturesPubl { assignedDomains.UnionWith(_domainService.GetAssignedDomains(ancestorID, true)); } - foreach (var data in _userDataService.GetUserData()) - { - _logger.LogWarning($"{data.Name} : {data.Data}"); - } + // No domains at all, add a warning, to add domains. if (assignedDomains.Count == 0) { globalNotifications.AddWarningNotification( _localizedTextService.Localize("auditTrails", "publish"), _localizedTextService.Localize("speechBubbles", "publishWithNoDomains")); - _logger.LogWarning("NOT REGISTRED DOMAIN FOR: {Cultures}"); + _logger.LogWarning("The root node {RootNodeName} was published with multiple cultures, but no domains are configured, this will cause routing and caching issues, please register domains for: {Cultures}", persistedContent.Name, string.Join(", ", publishedCultures)); return; From 6e540de40843744b7fb834114e7ae7e215f4c649 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 14 Oct 2021 14:36:58 +0200 Subject: [PATCH 24/32] Added additional logging --- .../Controllers/ContentControllerTests.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 540aa9172523..936835cac9ff 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -444,7 +444,24 @@ public async Task PostSave_Validates_Domains_Exist() body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); - TestContext.Progress.Write($"======CURRENT THREAD CULTURE IN TEST======{Thread.CurrentThread.CurrentCulture.Name}"); + var currentThreadCulture = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName; + var currentUICulture = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; + if (string.IsNullOrEmpty(currentThreadCulture)) + { + TestContext.Progress.Write($"======CURRENT THREAD CULTURE IN TEST====== NO CULTURE"); + } + else + { + TestContext.Progress.Write($"======CURRENT THREAD CULTURE IN TEST======{currentThreadCulture}"); + } + if (string.IsNullOrEmpty(currentUICulture)) + { + TestContext.Progress.Write($"======CURRENT UI CULTURE IN TEST====== NO CULTURE"); + } + else + { + TestContext.Progress.Write($"======CURRENT UI CULTURE IN TEST======{currentUICulture}"); + } ILocalizedTextService localizedTextService = GetRequiredService(); foreach (var culture in localizedTextService.GetSupportedCultures()) { From 885281fc896d3524c1d21bda47514064b86c0094 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:16:49 +0200 Subject: [PATCH 25/32] Added Assembly attributes --- .../Controllers/ContentControllerTests.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 936835cac9ff..e7797c9b900a 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -467,7 +467,7 @@ public async Task PostSave_Validates_Domains_Exist() { TestContext.Progress.Write($"======SUPPORTED CULTURES IN TEXT SERVICE======{culture.Name}"); } - var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithNoDomains", new []{"en-US"}); + var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithNoDomains"); Assert.Multiple(() => { @@ -476,7 +476,8 @@ public async Task PostSave_Validates_Domains_Exist() Assert.AreEqual(expectedMessage, display.Notifications.FirstOrDefault(x => x.NotificationType == NotificationStyle.Warning)?.Message); }); } - + [assembly: NUnit.Framework.SetCulture("se-SE")] + [assembly: NUnit.Framework.SetUICulture("se-SE")] [Test] public async Task PostSave_Validates_All_Cultures_Has_Domains() { @@ -530,7 +531,7 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() ILocalizedTextService localizedTextService = GetRequiredService(); - var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{"en-US"}); + var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain"); Assert.Multiple(() => { From d992a6e71fa2ec9e559afe4278b30d12d07d2e4e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:20:34 +0200 Subject: [PATCH 26/32] Changed default language for Unit & Integration tests to en-US --- src/Umbraco.Tests.Integration/AssemblyAttributes.cs | 4 ++++ src/Umbraco.Tests.UnitTests/AssemblyAttributes.cs | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 src/Umbraco.Tests.Integration/AssemblyAttributes.cs create mode 100644 src/Umbraco.Tests.UnitTests/AssemblyAttributes.cs diff --git a/src/Umbraco.Tests.Integration/AssemblyAttributes.cs b/src/Umbraco.Tests.Integration/AssemblyAttributes.cs new file mode 100644 index 000000000000..afa3bb903e73 --- /dev/null +++ b/src/Umbraco.Tests.Integration/AssemblyAttributes.cs @@ -0,0 +1,4 @@ +using NUnit.Framework; + +[assembly: SetCulture("en-US")] +[assembly: SetUICulture("en-US")] diff --git a/src/Umbraco.Tests.UnitTests/AssemblyAttributes.cs b/src/Umbraco.Tests.UnitTests/AssemblyAttributes.cs new file mode 100644 index 000000000000..afa3bb903e73 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/AssemblyAttributes.cs @@ -0,0 +1,4 @@ +using NUnit.Framework; + +[assembly: SetCulture("en-US")] +[assembly: SetUICulture("en-US")] From d184f82d63be3f0638cd718ff955587c806f4696 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 14 Oct 2021 15:31:10 +0200 Subject: [PATCH 27/32] Added removed language array back --- .../Controllers/ContentControllerTests.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index e7797c9b900a..2ec993b72f89 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -476,8 +476,6 @@ public async Task PostSave_Validates_Domains_Exist() Assert.AreEqual(expectedMessage, display.Notifications.FirstOrDefault(x => x.NotificationType == NotificationStyle.Warning)?.Message); }); } - [assembly: NUnit.Framework.SetCulture("se-SE")] - [assembly: NUnit.Framework.SetUICulture("se-SE")] [Test] public async Task PostSave_Validates_All_Cultures_Has_Domains() { @@ -531,7 +529,7 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() ILocalizedTextService localizedTextService = GetRequiredService(); - var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain"); + var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{"en-US"}); Assert.Multiple(() => { From 563babd5551b601bf685a560577febbca4456822 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 14 Oct 2021 16:01:54 +0200 Subject: [PATCH 28/32] Removed logging from tests --- .../Controllers/ContentControllerTests.cs | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 2ec993b72f89..7d9e2f149ebe 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -446,27 +446,7 @@ public async Task PostSave_Validates_Domains_Exist() var currentThreadCulture = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName; var currentUICulture = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; - if (string.IsNullOrEmpty(currentThreadCulture)) - { - TestContext.Progress.Write($"======CURRENT THREAD CULTURE IN TEST====== NO CULTURE"); - } - else - { - TestContext.Progress.Write($"======CURRENT THREAD CULTURE IN TEST======{currentThreadCulture}"); - } - if (string.IsNullOrEmpty(currentUICulture)) - { - TestContext.Progress.Write($"======CURRENT UI CULTURE IN TEST====== NO CULTURE"); - } - else - { - TestContext.Progress.Write($"======CURRENT UI CULTURE IN TEST======{currentUICulture}"); - } ILocalizedTextService localizedTextService = GetRequiredService(); - foreach (var culture in localizedTextService.GetSupportedCultures()) - { - TestContext.Progress.Write($"======SUPPORTED CULTURES IN TEXT SERVICE======{culture.Name}"); - } var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithNoDomains"); Assert.Multiple(() => From e0cac5e79eb01308fbb1b097be714451ea83cec3 Mon Sep 17 00:00:00 2001 From: Mole Date: Fri, 15 Oct 2021 08:26:23 +0200 Subject: [PATCH 29/32] Apply suggestions from code review Co-authored-by: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> --- .../Builders/ContentCultureInfosCollectionBuilder.cs | 2 +- .../Controllers/ContentControllerTests.cs | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs b/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs index ba1f7e357d69..a09088063341 100644 --- a/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs +++ b/src/Umbraco.Tests.Common/Builders/ContentCultureInfosCollectionBuilder.cs @@ -7,7 +7,7 @@ namespace Umbraco.Cms.Tests.Common.Builders { public class ContentCultureInfosCollectionBuilder : ChildBuilderBase, IBuildContentCultureInfosCollection { - private List _cultureInfosBuilders; + private readonly List _cultureInfosBuilders; public ContentCultureInfosCollectionBuilder(ContentBuilder parentBuilder) : base(parentBuilder) => _cultureInfosBuilders = new List(); public ContentCultureInfosBuilder AddCultureInfos() diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 7d9e2f149ebe..f2aff1b55498 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Umbraco. // See LICENSE for more details. -using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; @@ -440,12 +439,11 @@ public async Task PostSave_Validates_Domains_Exist() { { new StringContent(JsonConvert.SerializeObject(model)), "contentItem" } }); + var body = await response.Content.ReadAsStringAsync(); body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); - var currentThreadCulture = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName; - var currentUICulture = Thread.CurrentThread.CurrentUICulture.TwoLetterISOLanguageName; ILocalizedTextService localizedTextService = GetRequiredService(); var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithNoDomains"); From 28c9b9bcca12c963cea15749ceac87f6da1454d5 Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Fri, 15 Oct 2021 08:36:24 +0200 Subject: [PATCH 30/32] Add constants for ISO codes in ContentControllerTests --- .../Controllers/ContentControllerTests.cs | 59 +++++++++---------- 1 file changed, 28 insertions(+), 31 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 7d9e2f149ebe..eaa860fa054b 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -25,6 +25,9 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers [TestFixture] public class ContentControllerTests : UmbracoTestServerTestBase { + private const string UsIso = "en-US"; + private const string DkIso = "da-DK"; + /// /// Returns 404 if the content wasn't found based on the ID specified /// @@ -35,7 +38,7 @@ public async Task PostSave_Validate_Existing_Content() // Add another language localizationService.Save(new LanguageBuilder() - .WithCultureInfo("da-DK") + .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -93,7 +96,7 @@ public async Task PostSave_Validate_At_Least_One_Variant_Flagged_For_Saving() // Add another language localizationService.Save(new LanguageBuilder() - .WithCultureInfo("da-DK") + .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -162,7 +165,7 @@ public async Task PostSave_Validate_Properties_Exist() // Add another language localizationService.Save(new LanguageBuilder() - .WithCultureInfo("da-DK") + .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -227,7 +230,7 @@ public async Task PostSave_Simple_Invariant() // Add another language localizationService.Save(new LanguageBuilder() - .WithCultureInfo("da-DK") + .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -288,7 +291,7 @@ public async Task PostSave_Validate_Empty_Name() // Add another language localizationService.Save(new LanguageBuilder() - .WithCultureInfo("da-DK") + .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -352,7 +355,7 @@ public async Task PostSave_Validate_Variants_Empty_Name() // Add another language localizationService.Save(new LanguageBuilder() - .WithCultureInfo("da-DK") + .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -376,8 +379,8 @@ public async Task PostSave_Validate_Variants_Empty_Name() Content content = new ContentBuilder() .WithId(0) - .WithCultureName("en-US", "English") - .WithCultureName("da-DK", "Danish") + .WithCultureName(UsIso, "English") + .WithCultureName(DkIso, "Danish") .WithContentType(contentType) .AddPropertyData() .WithKeyValue("title", "Cool invariant title") @@ -414,7 +417,7 @@ public async Task PostSave_Validates_Domains_Exist() { ILocalizationService localizationService = GetRequiredService(); localizationService.Save(new LanguageBuilder() - .WithCultureInfo("da-DK") + .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -425,8 +428,8 @@ public async Task PostSave_Validates_Domains_Exist() Content content = new ContentBuilder() .WithId(1) .WithContentType(contentType) - .WithCultureName("en-US", "Root") - .WithCultureName("da-DK", "Rod") + .WithCultureName(UsIso, "Root") + .WithCultureName(DkIso, "Rod") .Build(); ContentItemSave model = new ContentItemSaveBuilder() @@ -459,12 +462,9 @@ public async Task PostSave_Validates_Domains_Exist() [Test] public async Task PostSave_Validates_All_Cultures_Has_Domains() { - var enString = "en-US"; - var dkString = "da-DK"; - ILocalizationService localizationService = GetRequiredService(); localizationService.Save(new LanguageBuilder() - .WithCultureInfo(dkString) + .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -475,8 +475,8 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() Content content = new ContentBuilder() .WithoutIdentity() .WithContentType(contentType) - .WithCultureName(enString, "Root") - .WithCultureName(dkString, "Rod") + .WithCultureName(UsIso, "Root") + .WithCultureName(DkIso, "Rod") .Build(); IContentService contentService = GetRequiredService(); @@ -487,7 +487,7 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() .WithAction(ContentSaveAction.Publish) .Build(); - ILanguage dkLanguage = localizationService.GetLanguageByIsoCode(dkString); + ILanguage dkLanguage = localizationService.GetLanguageByIsoCode(DkIso); IDomainService domainService = GetRequiredService(); var dkDomain = new UmbracoDomain("/") { @@ -509,7 +509,7 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() ILocalizedTextService localizedTextService = GetRequiredService(); - var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{"en-US"}); + var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{UsIso}); Assert.Multiple(() => { @@ -522,12 +522,9 @@ public async Task PostSave_Validates_All_Cultures_Has_Domains() [Test] public async Task PostSave_Checks_Ancestors_For_Domains() { - var enString = "en-US"; - var dkString = "da-DK"; - ILocalizationService localizationService = GetRequiredService(); localizationService.Save(new LanguageBuilder() - .WithCultureInfo(dkString) + .WithCultureInfo(DkIso) .WithIsDefault(false) .Build()); @@ -538,8 +535,8 @@ public async Task PostSave_Checks_Ancestors_For_Domains() Content rootNode = new ContentBuilder() .WithoutIdentity() .WithContentType(contentType) - .WithCultureName(enString, "Root") - .WithCultureName(dkString, "Rod") + .WithCultureName(UsIso, "Root") + .WithCultureName(DkIso, "Rod") .Build(); IContentService contentService = GetRequiredService(); @@ -549,8 +546,8 @@ public async Task PostSave_Checks_Ancestors_For_Domains() .WithoutIdentity() .WithParent(rootNode) .WithContentType(contentType) - .WithCultureName(dkString, "Barn") - .WithCultureName(enString, "Child") + .WithCultureName(DkIso, "Barn") + .WithCultureName(UsIso, "Child") .Build(); contentService.SaveAndPublish(childNode); @@ -559,14 +556,14 @@ public async Task PostSave_Checks_Ancestors_For_Domains() .WithoutIdentity() .WithParent(childNode) .WithContentType(contentType) - .WithCultureName(dkString, "BarneBarn") - .WithCultureName(enString, "GrandChild") + .WithCultureName(DkIso, "BarneBarn") + .WithCultureName(UsIso, "GrandChild") .Build(); contentService.Save(grandChild); - ILanguage dkLanguage = localizationService.GetLanguageByIsoCode(dkString); - ILanguage usLanguage = localizationService.GetLanguageByIsoCode(enString); + ILanguage dkLanguage = localizationService.GetLanguageByIsoCode(DkIso); + ILanguage usLanguage = localizationService.GetLanguageByIsoCode(UsIso); IDomainService domainService = GetRequiredService(); var dkDomain = new UmbracoDomain("/") { From cc74e3f4fd3885d87cc6f423d1c5530ce68e09de Mon Sep 17 00:00:00 2001 From: nikolajlauridsen Date: Fri, 15 Oct 2021 08:42:17 +0200 Subject: [PATCH 31/32] Remove unused using --- .../Controllers/ContentControllerTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index 344d8135354e..ee88029ef7df 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using NUnit.Framework; @@ -442,7 +441,7 @@ public async Task PostSave_Validates_Domains_Exist() { { new StringContent(JsonConvert.SerializeObject(model)), "contentItem" } }); - + var body = await response.Content.ReadAsStringAsync(); body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); ContentItemDisplay display = JsonConvert.DeserializeObject(body); From 48003e0b707499f39116aad2a2f148e931a70049 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle Date: Fri, 15 Oct 2021 11:47:48 +0200 Subject: [PATCH 32/32] Check ancestors for published culture Fixed issue where if you were 3 levels deep and published only 1 culture, warning wouldn't fire --- .../Controllers/ContentControllerTests.cs | 94 +++++++++++++++++++ .../Controllers/ContentController.cs | 23 ++++- 2 files changed, 116 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs index ee88029ef7df..b91e87907a6b 100644 --- a/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs +++ b/src/Umbraco.Tests.Integration/Umbraco.Web.BackOffice/Controllers/ContentControllerTests.cs @@ -456,6 +456,100 @@ public async Task PostSave_Validates_Domains_Exist() Assert.AreEqual(expectedMessage, display.Notifications.FirstOrDefault(x => x.NotificationType == NotificationStyle.Warning)?.Message); }); } + + [Test] + public async Task PostSave_Validates_All_Ancestor_Cultures_Are_Considered() + { + var sweIso = "sv-SE"; + ILocalizationService localizationService = GetRequiredService(); + //Create 2 new languages + localizationService.Save(new LanguageBuilder() + .WithCultureInfo(DkIso) + .WithIsDefault(false) + .Build()); + + localizationService.Save(new LanguageBuilder() + .WithCultureInfo(sweIso) + .WithIsDefault(false) + .Build()); + + IContentTypeService contentTypeService = GetRequiredService(); + IContentType contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build(); + contentTypeService.Save(contentType); + + Content content = new ContentBuilder() + .WithoutIdentity() + .WithContentType(contentType) + .WithCultureName(UsIso, "Root") + .Build(); + + IContentService contentService = GetRequiredService(); + contentService.SaveAndPublish(content); + + Content childContent = new ContentBuilder() + .WithoutIdentity() + .WithContentType(contentType) + .WithParent(content) + .WithCultureName(DkIso, "Barn") + .WithCultureName(UsIso, "Child") + .Build(); + + contentService.SaveAndPublish(childContent); + + Content grandChildContent = new ContentBuilder() + .WithoutIdentity() + .WithContentType(contentType) + .WithParent(childContent) + .WithCultureName(sweIso, "Bjarn") + .Build(); + + + ContentItemSave model = new ContentItemSaveBuilder() + .WithContent(grandChildContent) + .WithParentId(childContent.Id) + .WithAction(ContentSaveAction.PublishNew) + .Build(); + + ILanguage enLanguage = localizationService.GetLanguageByIsoCode(UsIso); + IDomainService domainService = GetRequiredService(); + var enDomain = new UmbracoDomain("/en") + { + RootContentId = content.Id, + LanguageId = enLanguage.Id + }; + domainService.Save(enDomain); + + ILanguage dkLanguage = localizationService.GetLanguageByIsoCode(DkIso); + var dkDomain = new UmbracoDomain("/dk") + { + RootContentId = childContent.Id, + LanguageId = dkLanguage.Id + }; + domainService.Save(dkDomain); + + var url = PrepareApiControllerUrl(x => x.PostSave(null)); + + HttpResponseMessage response = await Client.PostAsync(url, new MultipartFormDataContent + { + { new StringContent(JsonConvert.SerializeObject(model)), "contentItem" } + }); + + var body = await response.Content.ReadAsStringAsync(); + body = body.TrimStart(AngularJsonMediaTypeFormatter.XsrfPrefix); + ContentItemDisplay display = JsonConvert.DeserializeObject(body); + + + ILocalizedTextService localizedTextService = GetRequiredService(); + var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{"sv-SE"}); + + Assert.Multiple(() => + { + Assert.NotNull(display); + Assert.AreEqual(1, display.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning)); + Assert.AreEqual(expectedMessage, display.Notifications.FirstOrDefault(x => x.NotificationType == NotificationStyle.Warning)?.Message); + }); + } + [Test] public async Task PostSave_Validates_All_Cultures_Has_Domains() { diff --git a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs index 3f051d0a3587..70678545d9b2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/ContentController.cs @@ -1456,7 +1456,7 @@ internal void AddDomainWarnings(IContent persistedContent, string[] culturesPubl return; } - var publishedCultures = persistedContent.PublishedCultures.ToList(); + var publishedCultures = GetPublishedCulturesFromAncestors(persistedContent).ToList(); // If only a single culture is published we shouldn't have any routing issues if (publishedCultures.Count < 2) { @@ -1583,6 +1583,27 @@ private bool PublishCulture(IContent persistentContent, IEnumerable GetPublishedCulturesFromAncestors(IContent content) + { + if (content.ParentId == -1) + { + return content.PublishedCultures; + } + + HashSet publishedCultures = new (); + publishedCultures.UnionWith(content.PublishedCultures); + + IEnumerable ancestorIds = content.GetAncestorIds(); + + foreach (var id in ancestorIds) + { + IEnumerable cultures = _contentService.GetById(id).PublishedCultures; + publishedCultures.UnionWith(cultures); + } + + return publishedCultures; + + } /// /// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs ///