Merge remote-tracking branch 'origin/v9/dev' into v9/dev
This commit is contained in:
@@ -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 @@ namespace Umbraco.Extensions
|
||||
return ContentStatus.Unpublished;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection containing the ids of all ancestors.
|
||||
/// </summary>
|
||||
/// <param name="content"><see cref="IContent"/> to retrieve ancestors for</param>
|
||||
/// <returns>An Enumerable list of integer ids</returns>
|
||||
public static IEnumerable<int> 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
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Umbraco.Cms.Core.Security
|
||||
/// <summary>
|
||||
/// The user store for back office users
|
||||
/// </summary>
|
||||
public class BackOfficeUserStore : UmbracoUserStore<BackOfficeIdentityUser, IdentityRole<string>>
|
||||
public class BackOfficeUserStore : UmbracoUserStore<BackOfficeIdentityUser, IdentityRole<string>>, IUserSessionStore<BackOfficeIdentityUser>
|
||||
{
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
private readonly IUserService _userService;
|
||||
|
||||
@@ -531,18 +531,20 @@ namespace Umbraco.Cms.Core.Services.Implement
|
||||
public IEnumerable<IContent> GetAncestors(IContent content)
|
||||
{
|
||||
//null check otherwise we get exceptions
|
||||
if (content.Path.IsNullOrWhiteSpace()) return Enumerable.Empty<IContent>();
|
||||
|
||||
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();
|
||||
if (ids.Any() == false)
|
||||
return new List<IContent>();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
if (content.Path.IsNullOrWhiteSpace())
|
||||
{
|
||||
scope.ReadLock(Cms.Core.Constants.Locks.ContentTree);
|
||||
return Enumerable.Empty<IContent>();
|
||||
}
|
||||
|
||||
var ids = content.GetAncestorIds().ToArray();
|
||||
if (ids.Any() == false)
|
||||
{
|
||||
return new List<IContent>();
|
||||
}
|
||||
|
||||
using (IScope scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.ContentTree);
|
||||
return _documentRepository.GetMany(ids);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Umbraco.Cms.Tests.Common.Builders
|
||||
public class ContentBuilder
|
||||
: BuilderBase<Content>,
|
||||
IBuildContentTypes,
|
||||
IBuildContentCultureInfosCollection,
|
||||
IWithIdBuilder,
|
||||
IWithKeyBuilder,
|
||||
IWithParentIdBuilder,
|
||||
@@ -31,6 +32,7 @@ namespace Umbraco.Cms.Tests.Common.Builders
|
||||
IWithPropertyValues
|
||||
{
|
||||
private ContentTypeBuilder _contentTypeBuilder;
|
||||
private ContentCultureInfosCollectionBuilder _contentCultureInfosCollectionBuilder;
|
||||
private GenericDictionaryBuilder<ContentBuilder, string, object> _propertyDataBuilder;
|
||||
|
||||
private int? _id;
|
||||
@@ -48,6 +50,7 @@ namespace Umbraco.Cms.Tests.Common.Builders
|
||||
private bool? _trashed;
|
||||
private CultureInfo _cultureInfo;
|
||||
private IContentType _contentType;
|
||||
private ContentCultureInfosCollection _contentCultureInfosCollection;
|
||||
private readonly IDictionary<string, string> _cultureNames = new Dictionary<string, string>();
|
||||
private object _propertyValues;
|
||||
private string _propertyValuesCulture;
|
||||
@@ -73,6 +76,14 @@ namespace Umbraco.Cms.Tests.Common.Builders
|
||||
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 @@ namespace Umbraco.Cms.Tests.Common.Builders
|
||||
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 @@ namespace Umbraco.Cms.Tests.Common.Builders
|
||||
content.ResetDirtyProperties(false);
|
||||
}
|
||||
|
||||
if (_contentCultureInfosCollection is not null || _contentCultureInfosCollectionBuilder is not null)
|
||||
{
|
||||
ContentCultureInfosCollection contentCultureInfos =
|
||||
_contentCultureInfosCollection ?? _contentCultureInfosCollectionBuilder.Build();
|
||||
content.PublishCultureInfos = contentCultureInfos;
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ContentCultureInfosCollectionBuilder, ContentCultureInfos>,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
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<ContentBuilder, ContentCultureInfosCollection>, IBuildContentCultureInfosCollection
|
||||
{
|
||||
private readonly List<ContentCultureInfosBuilder> _cultureInfosBuilders;
|
||||
public ContentCultureInfosCollectionBuilder(ContentBuilder parentBuilder) : base(parentBuilder) => _cultureInfosBuilders = new List<ContentCultureInfosBuilder>();
|
||||
|
||||
public ContentCultureInfosBuilder AddCultureInfos()
|
||||
{
|
||||
var builder = new ContentCultureInfosBuilder(this);
|
||||
_cultureInfosBuilders.Add(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
public override ContentCultureInfosCollection Build()
|
||||
{
|
||||
if (_cultureInfosBuilders.Count < 1)
|
||||
{
|
||||
throw new InvalidOperationException("You must add at least one culture infos to the collection builder");
|
||||
}
|
||||
var cultureInfosCollection = new ContentCultureInfosCollection();
|
||||
|
||||
foreach (ContentCultureInfosBuilder cultureInfosBuilder in _cultureInfosBuilders)
|
||||
{
|
||||
cultureInfosCollection.Add(cultureInfosBuilder.Build());
|
||||
}
|
||||
|
||||
return cultureInfosCollection;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,5 +234,11 @@ namespace Umbraco.Cms.Tests.Common.Builders.Extensions
|
||||
builder.PropertyValuesSegment = segment;
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static T WithDate<T>(this T builder, DateTime date) where T : IWithDateBuilder
|
||||
{
|
||||
builder.Date = date;
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Umbraco.Cms.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IBuildContentCultureInfosCollection
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Cms.Tests.Common.Builders.Interfaces
|
||||
{
|
||||
public interface IWithDateBuilder
|
||||
{
|
||||
DateTime? Date { get; set; }
|
||||
}
|
||||
}
|
||||
4
src/Umbraco.Tests.Integration/AssemblyAttributes.cs
Normal file
4
src/Umbraco.Tests.Integration/AssemblyAttributes.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
[assembly: SetCulture("en-US")]
|
||||
[assembly: SetUICulture("en-US")]
|
||||
@@ -23,6 +23,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";
|
||||
|
||||
/// <summary>
|
||||
/// Returns 404 if the content wasn't found based on the ID specified
|
||||
/// </summary>
|
||||
@@ -33,7 +36,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
// Add another language
|
||||
localizationService.Save(new LanguageBuilder()
|
||||
.WithCultureInfo("da-DK")
|
||||
.WithCultureInfo(DkIso)
|
||||
.WithIsDefault(false)
|
||||
.Build());
|
||||
|
||||
@@ -91,7 +94,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
// Add another language
|
||||
localizationService.Save(new LanguageBuilder()
|
||||
.WithCultureInfo("da-DK")
|
||||
.WithCultureInfo(DkIso)
|
||||
.WithIsDefault(false)
|
||||
.Build());
|
||||
|
||||
@@ -160,7 +163,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
// Add another language
|
||||
localizationService.Save(new LanguageBuilder()
|
||||
.WithCultureInfo("da-DK")
|
||||
.WithCultureInfo(DkIso)
|
||||
.WithIsDefault(false)
|
||||
.Build());
|
||||
|
||||
@@ -225,7 +228,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
// Add another language
|
||||
localizationService.Save(new LanguageBuilder()
|
||||
.WithCultureInfo("da-DK")
|
||||
.WithCultureInfo(DkIso)
|
||||
.WithIsDefault(false)
|
||||
.Build());
|
||||
|
||||
@@ -286,7 +289,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
// Add another language
|
||||
localizationService.Save(new LanguageBuilder()
|
||||
.WithCultureInfo("da-DK")
|
||||
.WithCultureInfo(DkIso)
|
||||
.WithIsDefault(false)
|
||||
.Build());
|
||||
|
||||
@@ -350,7 +353,7 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
// Add another language
|
||||
localizationService.Save(new LanguageBuilder()
|
||||
.WithCultureInfo("da-DK")
|
||||
.WithCultureInfo(DkIso)
|
||||
.WithIsDefault(false)
|
||||
.Build());
|
||||
|
||||
@@ -374,8 +377,8 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers
|
||||
|
||||
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")
|
||||
@@ -406,5 +409,291 @@ namespace Umbraco.Cms.Tests.Integration.Umbraco.Web.BackOffice.Controllers
|
||||
CollectionAssert.Contains(display.Errors.Keys, "_content_variant_en-US_null_");
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PostSave_Validates_Domains_Exist()
|
||||
{
|
||||
ILocalizationService localizationService = GetRequiredService<ILocalizationService>();
|
||||
localizationService.Save(new LanguageBuilder()
|
||||
.WithCultureInfo(DkIso)
|
||||
.WithIsDefault(false)
|
||||
.Build());
|
||||
|
||||
IContentTypeService contentTypeService = GetRequiredService<IContentTypeService>();
|
||||
IContentType contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build();
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
Content content = new ContentBuilder()
|
||||
.WithId(1)
|
||||
.WithContentType(contentType)
|
||||
.WithCultureName(UsIso, "Root")
|
||||
.WithCultureName(DkIso, "Rod")
|
||||
.Build();
|
||||
|
||||
ContentItemSave model = new ContentItemSaveBuilder()
|
||||
.WithContent(content)
|
||||
.WithAction(ContentSaveAction.PublishNew)
|
||||
.Build();
|
||||
|
||||
var url = PrepareApiControllerUrl<ContentController>(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<ContentItemDisplay>(body);
|
||||
|
||||
ILocalizedTextService localizedTextService = GetRequiredService<ILocalizedTextService>();
|
||||
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.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<ILocalizationService>();
|
||||
//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<IContentTypeService>();
|
||||
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<IContentService>();
|
||||
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<IDomainService>();
|
||||
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<ContentController>(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<ContentItemDisplay>(body);
|
||||
|
||||
|
||||
ILocalizedTextService localizedTextService = GetRequiredService<ILocalizedTextService>();
|
||||
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()
|
||||
{
|
||||
ILocalizationService localizationService = GetRequiredService<ILocalizationService>();
|
||||
localizationService.Save(new LanguageBuilder()
|
||||
.WithCultureInfo(DkIso)
|
||||
.WithIsDefault(false)
|
||||
.Build());
|
||||
|
||||
IContentTypeService contentTypeService = GetRequiredService<IContentTypeService>();
|
||||
IContentType contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build();
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
Content content = new ContentBuilder()
|
||||
.WithoutIdentity()
|
||||
.WithContentType(contentType)
|
||||
.WithCultureName(UsIso, "Root")
|
||||
.WithCultureName(DkIso, "Rod")
|
||||
.Build();
|
||||
|
||||
IContentService contentService = GetRequiredService<IContentService>();
|
||||
contentService.Save(content);
|
||||
|
||||
ContentItemSave model = new ContentItemSaveBuilder()
|
||||
.WithContent(content)
|
||||
.WithAction(ContentSaveAction.Publish)
|
||||
.Build();
|
||||
|
||||
ILanguage dkLanguage = localizationService.GetLanguageByIsoCode(DkIso);
|
||||
IDomainService domainService = GetRequiredService<IDomainService>();
|
||||
var dkDomain = new UmbracoDomain("/")
|
||||
{
|
||||
RootContentId = content.Id,
|
||||
LanguageId = dkLanguage.Id
|
||||
};
|
||||
domainService.Save(dkDomain);
|
||||
|
||||
var url = PrepareApiControllerUrl<ContentController>(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<ContentItemDisplay>(body);
|
||||
|
||||
|
||||
ILocalizedTextService localizedTextService = GetRequiredService<ILocalizedTextService>();
|
||||
var expectedMessage = localizedTextService.Localize("speechBubbles", "publishWithMissingDomain", new []{UsIso});
|
||||
|
||||
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_Checks_Ancestors_For_Domains()
|
||||
{
|
||||
ILocalizationService localizationService = GetRequiredService<ILocalizationService>();
|
||||
localizationService.Save(new LanguageBuilder()
|
||||
.WithCultureInfo(DkIso)
|
||||
.WithIsDefault(false)
|
||||
.Build());
|
||||
|
||||
IContentTypeService contentTypeService = GetRequiredService<IContentTypeService>();
|
||||
IContentType contentType = new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build();
|
||||
contentTypeService.Save(contentType);
|
||||
|
||||
Content rootNode = new ContentBuilder()
|
||||
.WithoutIdentity()
|
||||
.WithContentType(contentType)
|
||||
.WithCultureName(UsIso, "Root")
|
||||
.WithCultureName(DkIso, "Rod")
|
||||
.Build();
|
||||
|
||||
IContentService contentService = GetRequiredService<IContentService>();
|
||||
contentService.SaveAndPublish(rootNode);
|
||||
|
||||
Content childNode = new ContentBuilder()
|
||||
.WithoutIdentity()
|
||||
.WithParent(rootNode)
|
||||
.WithContentType(contentType)
|
||||
.WithCultureName(DkIso, "Barn")
|
||||
.WithCultureName(UsIso, "Child")
|
||||
.Build();
|
||||
|
||||
contentService.SaveAndPublish(childNode);
|
||||
|
||||
Content grandChild = new ContentBuilder()
|
||||
.WithoutIdentity()
|
||||
.WithParent(childNode)
|
||||
.WithContentType(contentType)
|
||||
.WithCultureName(DkIso, "BarneBarn")
|
||||
.WithCultureName(UsIso, "GrandChild")
|
||||
.Build();
|
||||
|
||||
contentService.Save(grandChild);
|
||||
|
||||
ILanguage dkLanguage = localizationService.GetLanguageByIsoCode(DkIso);
|
||||
ILanguage usLanguage = localizationService.GetLanguageByIsoCode(UsIso);
|
||||
IDomainService domainService = GetRequiredService<IDomainService>();
|
||||
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<ContentController>(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<ContentItemDisplay>(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));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
src/Umbraco.Tests.UnitTests/AssemblyAttributes.cs
Normal file
4
src/Umbraco.Tests.UnitTests/AssemblyAttributes.cs
Normal file
@@ -0,0 +1,4 @@
|
||||
using NUnit.Framework;
|
||||
|
||||
[assembly: SetCulture("en-US")]
|
||||
[assembly: SetUICulture("en-US")]
|
||||
@@ -0,0 +1,273 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
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.Models.ContentEditing;
|
||||
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_Warning()
|
||||
{
|
||||
// Setup domain service
|
||||
var domainServiceMock = new Mock<IDomainService>();
|
||||
domainServiceMock.Setup(x => x.GetAssignedDomains(1060, It.IsAny<bool>()))
|
||||
.Returns(new []{new UmbracoDomain("/", "da-dk"), new UmbracoDomain("/en", "en-us")});
|
||||
|
||||
// Create content, we need to specify and ID in order to be able to configure domain service
|
||||
Content rootNode = new ContentBuilder()
|
||||
.WithContentType(CreateContentType())
|
||||
.WithId(1060)
|
||||
.AddContentCultureInfosCollection()
|
||||
.AddCultureInfos()
|
||||
.WithCultureIso("da-dk")
|
||||
.Done()
|
||||
.AddCultureInfos()
|
||||
.WithCultureIso("en-us")
|
||||
.Done()
|
||||
.Done()
|
||||
.Build();
|
||||
|
||||
var culturesPublished = new []{ "en-us", "da-dk" };
|
||||
var notifications = new SimpleNotificationModel();
|
||||
|
||||
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
||||
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
||||
|
||||
Assert.IsEmpty(notifications.Notifications);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Node_With_Single_Published_Culture_Causes_No_Warning()
|
||||
{
|
||||
var domainServiceMock = new Mock<IDomainService>();
|
||||
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
||||
.Returns(Enumerable.Empty<IDomain>());
|
||||
|
||||
Content rootNode = new ContentBuilder()
|
||||
.WithContentType(CreateContentType())
|
||||
.WithId(1060)
|
||||
.AddContentCultureInfosCollection()
|
||||
.AddCultureInfos()
|
||||
.WithCultureIso("da-dk")
|
||||
.Done()
|
||||
.Done()
|
||||
.Build();
|
||||
|
||||
var culturesPublished = new []{"da-dk" };
|
||||
var notifications = new SimpleNotificationModel();
|
||||
|
||||
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
||||
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
||||
|
||||
Assert.IsEmpty(notifications.Notifications);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Root_Node_Without_Domains_Causes_SingleWarning()
|
||||
{
|
||||
var domainServiceMock = new Mock<IDomainService>();
|
||||
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
||||
.Returns(Enumerable.Empty<IDomain>());
|
||||
|
||||
Content rootNode = new ContentBuilder()
|
||||
.WithContentType(CreateContentType())
|
||||
.WithId(1060)
|
||||
.AddContentCultureInfosCollection()
|
||||
.AddCultureInfos()
|
||||
.WithCultureIso("da-dk")
|
||||
.Done()
|
||||
.AddCultureInfos()
|
||||
.WithCultureIso("en-us")
|
||||
.Done()
|
||||
.Done()
|
||||
.Build();
|
||||
|
||||
var culturesPublished = new []{ "en-us", "da-dk" };
|
||||
var notifications = new SimpleNotificationModel();
|
||||
|
||||
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
||||
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
||||
Assert.AreEqual(1, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void One_Warning_Per_Culture_Being_Published()
|
||||
{
|
||||
var domainServiceMock = new Mock<IDomainService>();
|
||||
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
||||
.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 []{ "en-us", "da-dk", "nl-bk", "se-sv" };
|
||||
var notifications = new SimpleNotificationModel();
|
||||
|
||||
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
||||
contentController.AddDomainWarnings(rootNode, culturesPublished, notifications);
|
||||
Assert.AreEqual(3, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Ancestor_Domains_Counts()
|
||||
{
|
||||
var rootId = 1060;
|
||||
var level1Id = 1061;
|
||||
var level2Id = 1062;
|
||||
var level3Id = 1063;
|
||||
|
||||
var domainServiceMock = new Mock<IDomainService>();
|
||||
domainServiceMock.Setup(x => x.GetAssignedDomains(rootId, It.IsAny<bool>()))
|
||||
.Returns(new[] { new UmbracoDomain("/", "da-dk") });
|
||||
|
||||
domainServiceMock.Setup(x => x.GetAssignedDomains(level1Id, It.IsAny<bool>()))
|
||||
.Returns(new[] { new UmbracoDomain("/en", "en-us") });
|
||||
|
||||
domainServiceMock.Setup(x => x.GetAssignedDomains(level2Id, It.IsAny<bool>()))
|
||||
.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 []{ "en-us", "da-dk", "nl-bk", "se-sv", "de-de" };
|
||||
|
||||
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
||||
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, notifications.Notifications.Count(x => x.NotificationType == NotificationStyle.Warning));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Only_Warns_About_Cultures_Being_Published()
|
||||
{
|
||||
var domainServiceMock = new Mock<IDomainService>();
|
||||
domainServiceMock.Setup(x => x.GetAssignedDomains(It.IsAny<int>(), It.IsAny<bool>()))
|
||||
.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 []{ "en-us", "se-sv" };
|
||||
var notifications = new SimpleNotificationModel();
|
||||
|
||||
ContentController contentController = CreateContentController(domainServiceMock.Object);
|
||||
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, 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<ILocalizedTextService>();
|
||||
localizedTextServiceMock.Setup(x => x.Localize(It.IsAny<string>(),
|
||||
It.IsAny<string>(), It.IsAny<CultureInfo>(), It.IsAny<IDictionary<string, string>>()))
|
||||
.Returns(() => Guid.NewGuid().ToString());
|
||||
|
||||
var controller = new ContentController(
|
||||
Mock.Of<ICultureDictionary>(),
|
||||
NullLoggerFactory.Instance,
|
||||
Mock.Of<IShortStringHelper>(),
|
||||
Mock.Of<IEventMessagesFactory>(),
|
||||
localizedTextServiceMock.Object,
|
||||
new PropertyEditorCollection(new DataEditorCollection(() => null)),
|
||||
Mock.Of<IContentService>(),
|
||||
Mock.Of<IUserService>(),
|
||||
Mock.Of<IBackOfficeSecurityAccessor>(),
|
||||
Mock.Of<IContentTypeService>(),
|
||||
Mock.Of<IUmbracoMapper>(),
|
||||
Mock.Of<IPublishedUrlProvider>(),
|
||||
domainService,
|
||||
Mock.Of<IDataTypeService>(),
|
||||
Mock.Of<ILocalizationService>(),
|
||||
Mock.Of<IFileService>(),
|
||||
Mock.Of<INotificationService>(),
|
||||
new ActionCollection(() => null),
|
||||
Mock.Of<ISqlContext>(),
|
||||
Mock.Of<IJsonSerializer>(),
|
||||
Mock.Of<IScopeProvider>(),
|
||||
Mock.Of<IAuthorizationService>()
|
||||
);
|
||||
|
||||
return controller;
|
||||
}
|
||||
|
||||
private IContentType CreateContentType() =>
|
||||
new ContentTypeBuilder().WithContentVariation(ContentVariation.Culture).Build();
|
||||
}
|
||||
}
|
||||
@@ -398,7 +398,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
[HttpPost]
|
||||
public ActionResult<IDictionary<string, ContentItemDisplay>> 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);
|
||||
@@ -879,7 +879,9 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
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;
|
||||
@@ -896,6 +898,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
|
||||
var publishStatus = PublishBranchInternal(contentItem, false, cultureForInvariantErrors, out wasCancelled, out var successfulCultures).ToList();
|
||||
AddDomainWarnings(publishStatus, successfulCultures, globalNotifications);
|
||||
AddPublishStatusNotifications(publishStatus, globalNotifications, notifications, successfulCultures);
|
||||
}
|
||||
break;
|
||||
@@ -1412,6 +1415,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
var publishStatus = _contentService.SaveAndPublish(contentItem.PersistedContent, culturesToPublish, _backofficeSecurityAccessor.BackOfficeSecurity.CurrentUser.Id);
|
||||
wasCancelled = publishStatus.Result == PublishResultType.FailedPublishCancelledByEvent;
|
||||
successfulCultures = culturesToPublish;
|
||||
|
||||
return publishStatus;
|
||||
}
|
||||
else
|
||||
@@ -1425,6 +1429,73 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDomainWarnings(IEnumerable<PublishResult> publishResults, string[] culturesPublished,
|
||||
SimpleNotificationModel globalNotifications)
|
||||
{
|
||||
foreach (PublishResult publishResult in publishResults)
|
||||
{
|
||||
AddDomainWarnings(publishResult.Content, culturesPublished, globalNotifications);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that there's an appropriate domain setup for the published cultures
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 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
|
||||
/// </remarks>
|
||||
/// <param name="persistedContent"></param>
|
||||
/// <param name="culturesPublished"></param>
|
||||
/// <param name="globalNotifications"></param>
|
||||
internal void AddDomainWarnings(IContent persistedContent, string[] culturesPublished, SimpleNotificationModel globalNotifications)
|
||||
{
|
||||
// Don't try to verify if no cultures were published
|
||||
if (culturesPublished is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var publishedCultures = GetPublishedCulturesFromAncestors(persistedContent).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
|
||||
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.UnionWith(_domainService.GetAssignedDomains(ancestorID, true));
|
||||
}
|
||||
|
||||
// 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("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;
|
||||
}
|
||||
|
||||
// If there is some domains, verify that there's a domain for each of the published cultures
|
||||
foreach (var culture in culturesPublished
|
||||
.Where(culture => assignedDomains.Any(x => x.LanguageIsoCode.Equals(culture, StringComparison.OrdinalIgnoreCase)) is false))
|
||||
{
|
||||
globalNotifications.AddWarningNotification(
|
||||
_localizedTextService.Localize("auditTrails", "publish"),
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validate if publishing is possible based on the mandatory language requirements
|
||||
/// </summary>
|
||||
@@ -1512,6 +1583,27 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetPublishedCulturesFromAncestors(IContent content)
|
||||
{
|
||||
if (content.ParentId == -1)
|
||||
{
|
||||
return content.PublishedCultures;
|
||||
}
|
||||
|
||||
HashSet<string> publishedCultures = new ();
|
||||
publishedCultures.UnionWith(content.PublishedCultures);
|
||||
|
||||
IEnumerable<int> ancestorIds = content.GetAncestorIds();
|
||||
|
||||
foreach (var id in ancestorIds)
|
||||
{
|
||||
IEnumerable<string> cultures = _contentService.GetById(id).PublishedCultures;
|
||||
publishedCultures.UnionWith(cultures);
|
||||
}
|
||||
|
||||
return publishedCultures;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs
|
||||
/// </summary>
|
||||
|
||||
@@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Cms.Core;
|
||||
@@ -93,6 +94,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
|
||||
IUserService userService,
|
||||
IUmbracoMapper umbracoMapper,
|
||||
IBackOfficeUserManager backOfficeUserManager,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILocalizedTextService localizedTextService,
|
||||
AppCaches appCaches,
|
||||
IShortStringHelper shortStringHelper,
|
||||
|
||||
@@ -1245,6 +1245,8 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="scheduleErrReleaseDate3">Kan ikke planlægge dokumentes udgivelse da det krævet '%0%' har en senere udgivelses dato end et ikke krævet sprog</key>
|
||||
<key alias="scheduleErrExpireDate1">Afpubliceringsdatoen kan ikke ligge i fortiden</key>
|
||||
<key alias="scheduleErrExpireDate2">Afpubliceringsdatoen kan ikke være før udgivelsesdatoen</key>
|
||||
<key alias="publishWithNoDomains">Domæner er ikke konfigureret for en flersproget side, kontakt vensligst en administrator, se loggen for mere information</key>
|
||||
<key alias="publishWithMissingDomain">Der er ikke noget domæne konfigureret for %0%, kontakt vensligst en administrator, se loggen for mere information</key>
|
||||
</area>
|
||||
<area alias="stylesheet">
|
||||
<key alias="addRule">Tilføj style</key>
|
||||
|
||||
@@ -1441,6 +1441,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="resendInviteSuccess">Invitation has been re-sent to %0%</key>
|
||||
<key alias="documentTypeExportedSuccess">Document Type was exported to file</key>
|
||||
<key alias="documentTypeExportedError">An error occurred while exporting the Document Type</key>
|
||||
<key alias="publishWithNoDomains">Domains are not configured for multilingual site, please contact an administrator, see log for more information</key>
|
||||
<key alias="publishWithMissingDomain">There is no domain configured for %0%, please contact an administrator, see log for more information</key>
|
||||
</area>
|
||||
<area alias="stylesheet">
|
||||
<key alias="addRule">Add style</key>
|
||||
|
||||
@@ -1470,6 +1470,8 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
|
||||
<key alias="scheduleErrReleaseDate3">Cannot schedule the document for publishing since the required '%0%' has a publish date later than a non mandatory language</key>
|
||||
<key alias="scheduleErrExpireDate1">The expire date cannot be in the past</key>
|
||||
<key alias="scheduleErrExpireDate2">The expire date cannot be before the release date</key>
|
||||
<key alias="publishWithNoDomains">Domains are not configured for multilingual site, please contact an administrator, see log for more information</key>
|
||||
<key alias="publishWithMissingDomain">There is no domain configured for %0%, please contact an administrator, see log for more information</key>
|
||||
</area>
|
||||
<area alias="stylesheet">
|
||||
<key alias="addRule">Add style</key>
|
||||
|
||||
Reference in New Issue
Block a user