Merge branch 'u4-11502' of https://github.com/AndyButland/Umbraco-CMS into temp8-11502
This commit is contained in:
@@ -113,6 +113,7 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
|
||||
Chain<ContentVariationMigration>("{1350617A-4930-4D61-852F-E3AA9E692173}");
|
||||
Chain<UpdateUmbracoConsent>("{39E5B1F7-A50B-437E-B768-1723AEC45B65}"); // from 7.12.0
|
||||
Chain<FallbackLanguage>("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}");
|
||||
//FINAL
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a new, self-joined field to umbracoLanguages to hold the fall-back language for
|
||||
/// a given language.
|
||||
/// </summary>
|
||||
public class FallbackLanguage : MigrationBase
|
||||
{
|
||||
public FallbackLanguage(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
var columns = SqlSyntax.GetColumnsInSchema(Context.Database).ToArray();
|
||||
|
||||
if (columns.Any(x => x.TableName.InvariantEquals(Constants.DatabaseSchema.Tables.Language) && x.ColumnName.InvariantEquals("fallbackLanguageId")) == false)
|
||||
AddColumn<LanguageDto>("fallbackLanguageId");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,5 +33,11 @@ namespace Umbraco.Core.Models
|
||||
/// If true, a variant node cannot be published unless this language variant is created
|
||||
/// </summary>
|
||||
bool Mandatory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines the id of a fallback language that can be used in multi-lingual scenarios to provide
|
||||
/// content if the requested language does not have it published.
|
||||
/// </summary>
|
||||
int? FallbackLanguageId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ namespace Umbraco.Core.Models
|
||||
private string _cultureName;
|
||||
private bool _isDefaultVariantLanguage;
|
||||
private bool _mandatory;
|
||||
private int? _fallbackLanguageId;
|
||||
|
||||
public Language(string isoCode)
|
||||
{
|
||||
@@ -32,6 +33,7 @@ namespace Umbraco.Core.Models
|
||||
public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo<Language, string>(x => x.CultureName);
|
||||
public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.IsDefaultVariantLanguage);
|
||||
public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo<Language, bool>(x => x.Mandatory);
|
||||
public readonly PropertyInfo FallbackLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, int?>(x => x.FallbackLanguageId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -71,5 +73,11 @@ namespace Umbraco.Core.Models
|
||||
get => _mandatory;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector);
|
||||
}
|
||||
|
||||
public int? FallbackLanguageId
|
||||
{
|
||||
get => _fallbackLanguageId;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, Ps.Value.FallbackLanguageSelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
public enum PublishedValueFallbackPriority
|
||||
{
|
||||
RecursiveTree,
|
||||
FallbackLanguage
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a fallback strategy for getting <see cref="IPublishedElement"/> values.
|
||||
/// </summary>
|
||||
@@ -30,8 +36,8 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
|
||||
T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue);
|
||||
|
||||
object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse);
|
||||
object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse, PublishedValueFallbackPriority fallbackPriority);
|
||||
|
||||
T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse);
|
||||
T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse, PublishedValueFallbackPriority fallbackPriority);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,9 +21,9 @@
|
||||
public T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue) => defaultValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse) => defaultValue;
|
||||
public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse, PublishedValueFallbackPriority fallbackPriority) => defaultValue;
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse) => defaultValue;
|
||||
public T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse, PublishedValueFallbackPriority fallbackPriority) => defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,5 +38,14 @@ namespace Umbraco.Core.Persistence.Dtos
|
||||
[Column("mandatory")]
|
||||
[Constraint(Default = "0")]
|
||||
public bool Mandatory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Defines the fallback language that can be used in multi-lingual scenarios to provide
|
||||
/// content if the requested language does not have it published.
|
||||
/// </summary>
|
||||
[Column("fallbackLanguageId")]
|
||||
[ForeignKey(typeof(LanguageDto), Column = "id")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
public int? FallbackLanguageId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
{
|
||||
public static ILanguage BuildEntity(LanguageDto dto)
|
||||
{
|
||||
var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id, IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage, Mandatory = dto.Mandatory };
|
||||
var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id, IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage, Mandatory = dto.Mandatory, FallbackLanguageId = dto.FallbackLanguageId };
|
||||
// reset dirty initial properties (U4-1946)
|
||||
lang.ResetDirtyProperties(false);
|
||||
return lang;
|
||||
@@ -16,9 +16,11 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
public static LanguageDto BuildDto(ILanguage entity)
|
||||
{
|
||||
var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode, IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage, Mandatory = entity.Mandatory };
|
||||
var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode, IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage, Mandatory = entity.Mandatory, FallbackLanguageId = entity.FallbackLanguageId };
|
||||
if (entity.HasIdentity)
|
||||
{
|
||||
dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture));
|
||||
}
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
sql.OrderBy<LanguageDto>(dto => dto.Id);
|
||||
|
||||
// get languages
|
||||
var languages = Database.Fetch<LanguageDto>(sql).Select(ConvertFromDto).OrderBy(x => x.Id).ToList();
|
||||
var dtos = Database.Fetch<LanguageDto>(sql);
|
||||
var languages = dtos.Select(ConvertFromDto).ToList();
|
||||
|
||||
// fix inconsistencies: there has to be a default language, and it has to be mandatory
|
||||
var defaultLanguage = languages.FirstOrDefault(x => x.IsDefaultVariantLanguage) ?? languages.First();
|
||||
@@ -79,7 +80,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var sqlClause = GetBaseQuery(false);
|
||||
var translator = new SqlTranslator<ILanguage>(sqlClause, query);
|
||||
var sql = translator.Translate();
|
||||
return Database.Fetch<LanguageDto>(sql).Select(ConvertFromDto);
|
||||
var dtos = Database.Fetch<LanguageDto>(sql);
|
||||
return dtos.Select(ConvertFromDto).ToList();
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -144,14 +146,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
IsolatedCache.ClearAllCache();
|
||||
}
|
||||
|
||||
;
|
||||
var dto = LanguageFactory.BuildDto(entity);
|
||||
|
||||
var id = Convert.ToInt32(Database.Insert(dto));
|
||||
entity.Id = id;
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
}
|
||||
|
||||
protected override void PersistUpdatedItem(ILanguage entity)
|
||||
@@ -204,7 +204,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
var entity = LanguageFactory.BuildEntity(dto);
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
||||
public ILanguage GetByIsoCode(string isoCode)
|
||||
{
|
||||
TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way
|
||||
|
||||
@@ -335,6 +335,7 @@
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\RefactorMacroColumns.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\SuperZero.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\TagsMigration.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\FallbackLanguage.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\UserForeignKeys.cs" />
|
||||
<Compile Include="Models\AuditEntry.cs" />
|
||||
<Compile Include="Models\Consent.cs" />
|
||||
|
||||
@@ -47,6 +47,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(language.HasIdentity, Is.True);
|
||||
Assert.That(language.CultureName, Is.EqualTo("en-US"));
|
||||
Assert.That(language.IsoCode, Is.EqualTo("en-US"));
|
||||
Assert.That(language.FallbackLanguageId, Is.Null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +62,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var au = CultureInfo.GetCultureInfo("en-AU");
|
||||
var language = (ILanguage)new Language(au.Name)
|
||||
{
|
||||
CultureName = au.DisplayName
|
||||
CultureName = au.DisplayName,
|
||||
FallbackLanguageId = 1
|
||||
};
|
||||
repository.Save(language);
|
||||
|
||||
@@ -73,6 +75,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(language.HasIdentity, Is.True);
|
||||
Assert.That(language.CultureName, Is.EqualTo(au.DisplayName));
|
||||
Assert.That(language.IsoCode, Is.EqualTo(au.Name));
|
||||
Assert.That(language.FallbackLanguageId, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,7 +185,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
// Act
|
||||
var languageBR = new Language("pt-BR") {CultureName = "pt-BR"};
|
||||
var languageBR = new Language("pt-BR") { CultureName = "pt-BR" };
|
||||
repository.Save(languageBR);
|
||||
|
||||
// Assert
|
||||
@@ -190,6 +193,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(languageBR.Id, Is.EqualTo(6)); //With 5 existing entries the Id should be 6
|
||||
Assert.IsFalse(languageBR.IsDefaultVariantLanguage);
|
||||
Assert.IsFalse(languageBR.Mandatory);
|
||||
Assert.IsNull(languageBR.FallbackLanguageId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,6 +215,31 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(languageBR.Id, Is.EqualTo(6)); //With 5 existing entries the Id should be 6
|
||||
Assert.IsTrue(languageBR.IsDefaultVariantLanguage);
|
||||
Assert.IsTrue(languageBR.Mandatory);
|
||||
Assert.IsNull(languageBR.FallbackLanguageId);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Perform_Add_On_LanguageRepository_With_Fallback_Language()
|
||||
{
|
||||
// Arrange
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
// Act
|
||||
var languageBR = new Language("pt-BR")
|
||||
{
|
||||
CultureName = "pt-BR",
|
||||
FallbackLanguageId = 1
|
||||
};
|
||||
repository.Save(languageBR);
|
||||
|
||||
// Assert
|
||||
Assert.That(languageBR.HasIdentity, Is.True);
|
||||
Assert.That(languageBR.Id, Is.EqualTo(6)); //With 5 existing entries the Id should be 6
|
||||
Assert.That(languageBR.FallbackLanguageId, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,13 +261,11 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.IsTrue(languageBR.Mandatory);
|
||||
|
||||
// Act
|
||||
|
||||
var languageNZ = new Language("en-NZ") { CultureName = "en-NZ", IsDefaultVariantLanguage = true, Mandatory = true };
|
||||
repository.Save(languageNZ);
|
||||
languageBR = repository.Get(languageBR.Id);
|
||||
|
||||
// Assert
|
||||
|
||||
Assert.IsFalse(languageBR.IsDefaultVariantLanguage);
|
||||
Assert.IsTrue(languageNZ.IsDefaultVariantLanguage);
|
||||
}
|
||||
@@ -257,6 +284,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var language = repository.Get(5);
|
||||
language.IsoCode = "pt-BR";
|
||||
language.CultureName = "pt-BR";
|
||||
language.FallbackLanguageId = 1;
|
||||
|
||||
repository.Save(language);
|
||||
|
||||
@@ -266,6 +294,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(languageUpdated, Is.Not.Null);
|
||||
Assert.That(languageUpdated.IsoCode, Is.EqualTo("pt-BR"));
|
||||
Assert.That(languageUpdated.CultureName, Is.EqualTo("pt-BR"));
|
||||
Assert.That(languageUpdated.FallbackLanguageId, Is.EqualTo(1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +343,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
base.TearDown();
|
||||
}
|
||||
|
||||
public void CreateTestData()
|
||||
private void CreateTestData()
|
||||
{
|
||||
var languageDK = new Language("da-DK") { CultureName = "da-DK" };
|
||||
ServiceContext.LocalizationService.Save(languageDK);//Id 2
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(PluginManager = UmbracoTestOptions.PluginManager.PerFixture)]
|
||||
public class PublishedContentLanuageVariantTests : PublishedContentSnapshotTestBase
|
||||
{
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
|
||||
Container.RegisterSingleton(_ => GetServiceContext());
|
||||
}
|
||||
|
||||
protected ServiceContext GetServiceContext()
|
||||
{
|
||||
var serviceContext = TestObjects.GetServiceContextMock(Container);
|
||||
MockLocalizationService(serviceContext);
|
||||
return serviceContext;
|
||||
}
|
||||
|
||||
private static void MockLocalizationService(ServiceContext serviceContext)
|
||||
{
|
||||
// Set up languages.
|
||||
// Spanish falls back to English and Italian to Spanish (and then to English).
|
||||
// French has no fall back.
|
||||
var languages = new List<Language>
|
||||
{
|
||||
new Language("en-US") { Id = 1, CultureName = "English", IsDefaultVariantLanguage = true },
|
||||
new Language("fr") { Id = 2, CultureName = "French" },
|
||||
new Language("es") { Id = 3, CultureName = "Spanish", FallbackLanguageId = 1 },
|
||||
new Language("it") { Id = 4, CultureName = "Italian", FallbackLanguageId = 3 },
|
||||
new Language("de") { Id = 5, CultureName = "German" }
|
||||
};
|
||||
|
||||
var localizationService = Mock.Get(serviceContext.LocalizationService);
|
||||
localizationService.Setup(x => x.GetAllLanguages()).Returns(languages);
|
||||
localizationService.Setup(x => x.GetLanguageById(It.IsAny<int>()))
|
||||
.Returns((int id) => languages.SingleOrDefault(y => y.Id == id));
|
||||
localizationService.Setup(x => x.GetLanguageByIsoCode(It.IsAny<string>()))
|
||||
.Returns((string c) => languages.SingleOrDefault(y => y.IsoCode == c));
|
||||
}
|
||||
|
||||
internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache)
|
||||
{
|
||||
var props = new[]
|
||||
{
|
||||
factory.CreatePropertyType("prop1", 1),
|
||||
};
|
||||
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty<string>(), props);
|
||||
|
||||
var prop1 = new SolidPublishedPropertyWithLanguageVariants
|
||||
{
|
||||
Alias = "welcomeText",
|
||||
};
|
||||
prop1.SetSourceValue("en-US", "Welcome");
|
||||
prop1.SetValue("en-US", "Welcome");
|
||||
prop1.SetSourceValue("de", "Willkommen");
|
||||
prop1.SetValue("de", "Willkommen");
|
||||
|
||||
cache.Add(new SolidPublishedContent(contentType1)
|
||||
{
|
||||
Id = 1,
|
||||
SortOrder = 0,
|
||||
Name = "Content 1",
|
||||
UrlSegment = "content-1",
|
||||
Path = "/1",
|
||||
Level = 1,
|
||||
Url = "/content-1",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
prop1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Populated_Requested_Language()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "en-US");
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Populated_Requested_Non_Default_Language()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "de");
|
||||
Assert.AreEqual("Willkommen", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Do_Not_Get_Content_For_Unpopulated_Requested_Language_Without_Fallback()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "fr");
|
||||
Assert.IsNull(value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "es");
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Content_For_Unpopulated_Requested_Language_With_Fallback_Over_Two_Levels()
|
||||
{
|
||||
var content = UmbracoContext.Current.ContentCache.GetAtRoot().First();
|
||||
var value = content.Value("welcomeText", "it");
|
||||
Assert.AreEqual("Welcome", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Web.Routing;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Core.Composing;
|
||||
using Current = Umbraco.Core.Composing.Current;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Tests.Testing.Objects.Accessors;
|
||||
|
||||
namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(PluginManager = UmbracoTestOptions.PluginManager.PerFixture)]
|
||||
public class PublishedContentMoreTests : PublishedContentTestBase
|
||||
public class PublishedContentMoreTests : PublishedContentSnapshotTestBase
|
||||
{
|
||||
// read http://stackoverflow.com/questions/7713326/extension-method-that-works-on-ienumerablet-and-iqueryablet
|
||||
// and http://msmvps.com/blogs/jon_skeet/archive/2010/10/28/overloading-and-generic-constraints.aspx
|
||||
// and http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx
|
||||
|
||||
public override void SetUp()
|
||||
internal override void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache)
|
||||
{
|
||||
base.SetUp();
|
||||
var props = new[]
|
||||
{
|
||||
factory.CreatePropertyType("prop1", 1),
|
||||
};
|
||||
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty<string>(), props);
|
||||
var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty<string>(), props);
|
||||
var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty<string>(), props);
|
||||
|
||||
var umbracoContext = GetUmbracoContext();
|
||||
Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext;
|
||||
}
|
||||
cache.Add(new SolidPublishedContent(contentType1)
|
||||
{
|
||||
Id = 1,
|
||||
SortOrder = 0,
|
||||
Name = "Content 1",
|
||||
UrlSegment = "content-1",
|
||||
Path = "/1",
|
||||
Level = 1,
|
||||
Url = "/content-1",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
cache.Add(new SolidPublishedContent(contentType2)
|
||||
{
|
||||
Id = 2,
|
||||
SortOrder = 1,
|
||||
Name = "Content 2",
|
||||
UrlSegment = "content-2",
|
||||
Path = "/2",
|
||||
Level = 1,
|
||||
Url = "/content-2",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Container.RegisterSingleton<IPublishedModelFactory>(f => new PublishedModelFactory(f.GetInstance<TypeLoader>().GetTypes<PublishedContentModel>()));
|
||||
}
|
||||
|
||||
protected override TypeLoader CreatePluginManager(IServiceFactory f)
|
||||
{
|
||||
var pluginManager = base.CreatePluginManager(f);
|
||||
|
||||
// this is so the model factory looks into the test assembly
|
||||
pluginManager.AssembliesToScan = pluginManager.AssembliesToScan
|
||||
.Union(new[] { typeof (PublishedContentMoreTests).Assembly })
|
||||
.ToList();
|
||||
|
||||
return pluginManager;
|
||||
}
|
||||
|
||||
private UmbracoContext GetUmbracoContext()
|
||||
{
|
||||
RouteData routeData = null;
|
||||
|
||||
var publishedSnapshot = CreatePublishedSnapshot();
|
||||
|
||||
var publishedSnapshotService = new Mock<IPublishedSnapshotService>();
|
||||
publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(publishedSnapshot);
|
||||
|
||||
var globalSettings = TestObjects.GetGlobalSettings();
|
||||
|
||||
var httpContext = GetHttpContextFactory("http://umbraco.local/", routeData).HttpContext;
|
||||
var umbracoContext = new UmbracoContext(
|
||||
httpContext,
|
||||
publishedSnapshotService.Object,
|
||||
new WebSecurity(httpContext, Current.Services.UserService, globalSettings),
|
||||
TestObjects.GetUmbracoSettings(),
|
||||
Enumerable.Empty<IUrlProvider>(),
|
||||
globalSettings,
|
||||
new TestVariationContextAccessor());
|
||||
|
||||
return umbracoContext;
|
||||
}
|
||||
|
||||
public override void TearDown()
|
||||
{
|
||||
base.TearDown();
|
||||
|
||||
Current.Reset();
|
||||
cache.Add(new SolidPublishedContent(contentType2Sub)
|
||||
{
|
||||
Id = 3,
|
||||
SortOrder = 2,
|
||||
Name = "Content 2Sub",
|
||||
UrlSegment = "content-2sub",
|
||||
Path = "/3",
|
||||
Level = 1,
|
||||
Url = "/content-2sub",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -197,95 +200,5 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.AreEqual(1, result[0].Id);
|
||||
Assert.AreEqual(2, result[1].Id);
|
||||
}
|
||||
|
||||
private static SolidPublishedSnapshot CreatePublishedSnapshot()
|
||||
{
|
||||
var dataTypeService = new TestObjects.TestDataTypeService(
|
||||
new DataType(new VoidEditor(Mock.Of<ILogger>())) { Id = 1 });
|
||||
|
||||
var factory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(Array.Empty<IPropertyValueConverter>()), dataTypeService);
|
||||
var caches = new SolidPublishedSnapshot();
|
||||
var cache = caches.InnerContentCache;
|
||||
|
||||
var props = new[]
|
||||
{
|
||||
factory.CreatePropertyType("prop1", 1),
|
||||
};
|
||||
|
||||
var contentType1 = factory.CreateContentType(1, "ContentType1", Enumerable.Empty<string>(), props);
|
||||
var contentType2 = factory.CreateContentType(2, "ContentType2", Enumerable.Empty<string>(), props);
|
||||
var contentType2Sub = factory.CreateContentType(3, "ContentType2Sub", Enumerable.Empty<string>(), props);
|
||||
|
||||
cache.Add(new SolidPublishedContent(contentType1)
|
||||
{
|
||||
Id = 1,
|
||||
SortOrder = 0,
|
||||
Name = "Content 1",
|
||||
UrlSegment = "content-1",
|
||||
Path = "/1",
|
||||
Level = 1,
|
||||
Url = "/content-1",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] {},
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cache.Add(new SolidPublishedContent(contentType2)
|
||||
{
|
||||
Id = 2,
|
||||
SortOrder = 1,
|
||||
Name = "Content 2",
|
||||
UrlSegment = "content-2",
|
||||
Path = "/2",
|
||||
Level = 1,
|
||||
Url = "/content-2",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
cache.Add(new SolidPublishedContent(contentType2Sub)
|
||||
{
|
||||
Id = 3,
|
||||
SortOrder = 2,
|
||||
Name = "Content 2Sub",
|
||||
UrlSegment = "content-2sub",
|
||||
Path = "/3",
|
||||
Level = 1,
|
||||
Url = "/content-2sub",
|
||||
ParentId = -1,
|
||||
ChildIds = new int[] { },
|
||||
Properties = new Collection<IPublishedProperty>
|
||||
{
|
||||
new SolidPublishedProperty
|
||||
{
|
||||
Alias = "prop1",
|
||||
SolidHasValue = true,
|
||||
SolidValue = 1234,
|
||||
SolidSourceValue = "1234"
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return caches;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Web.Routing;
|
||||
using Moq;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Core.Composing;
|
||||
using Current = Umbraco.Core.Composing.Current;
|
||||
using LightInject;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing.Objects.Accessors;
|
||||
|
||||
namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
public abstract class PublishedContentSnapshotTestBase : PublishedContentTestBase
|
||||
{
|
||||
// read http://stackoverflow.com/questions/7713326/extension-method-that-works-on-ienumerablet-and-iqueryablet
|
||||
// and http://msmvps.com/blogs/jon_skeet/archive/2010/10/28/overloading-and-generic-constraints.aspx
|
||||
// and http://blogs.msdn.com/b/ericlippert/archive/2009/12/10/constraints-are-not-part-of-the-signature.aspx
|
||||
|
||||
public override void SetUp()
|
||||
{
|
||||
base.SetUp();
|
||||
|
||||
var umbracoContext = GetUmbracoContext();
|
||||
Umbraco.Web.Composing.Current.UmbracoContextAccessor.UmbracoContext = umbracoContext;
|
||||
}
|
||||
|
||||
protected override void Compose()
|
||||
{
|
||||
base.Compose();
|
||||
|
||||
Container.RegisterSingleton<IPublishedModelFactory>(f => new PublishedModelFactory(f.GetInstance<TypeLoader>().GetTypes<PublishedContentModel>()));
|
||||
}
|
||||
|
||||
protected override TypeLoader CreatePluginManager(IServiceFactory f)
|
||||
{
|
||||
var pluginManager = base.CreatePluginManager(f);
|
||||
|
||||
// this is so the model factory looks into the test assembly
|
||||
pluginManager.AssembliesToScan = pluginManager.AssembliesToScan
|
||||
.Union(new[] { typeof (PublishedContentMoreTests).Assembly })
|
||||
.ToList();
|
||||
|
||||
return pluginManager;
|
||||
}
|
||||
|
||||
private UmbracoContext GetUmbracoContext()
|
||||
{
|
||||
RouteData routeData = null;
|
||||
|
||||
var publishedSnapshot = CreatePublishedSnapshot();
|
||||
|
||||
var publishedSnapshotService = new Mock<IPublishedSnapshotService>();
|
||||
publishedSnapshotService.Setup(x => x.CreatePublishedSnapshot(It.IsAny<string>())).Returns(publishedSnapshot);
|
||||
|
||||
var globalSettings = TestObjects.GetGlobalSettings();
|
||||
|
||||
var httpContext = GetHttpContextFactory("http://umbraco.local/", routeData).HttpContext;
|
||||
var umbracoContext = new UmbracoContext(
|
||||
httpContext,
|
||||
publishedSnapshotService.Object,
|
||||
new WebSecurity(httpContext, Current.Services.UserService, globalSettings),
|
||||
TestObjects.GetUmbracoSettings(),
|
||||
Enumerable.Empty<IUrlProvider>(),
|
||||
globalSettings,
|
||||
new TestVariationContextAccessor());
|
||||
|
||||
return umbracoContext;
|
||||
}
|
||||
|
||||
public override void TearDown()
|
||||
{
|
||||
base.TearDown();
|
||||
|
||||
Current.Reset();
|
||||
}
|
||||
|
||||
private SolidPublishedSnapshot CreatePublishedSnapshot()
|
||||
{
|
||||
var dataTypeService = new TestObjects.TestDataTypeService(
|
||||
new DataType(new VoidEditor(Mock.Of<ILogger>())) { Id = 1 });
|
||||
|
||||
var factory = new PublishedContentTypeFactory(Mock.Of<IPublishedModelFactory>(), new PropertyValueConverterCollection(Array.Empty<IPropertyValueConverter>()), dataTypeService);
|
||||
var caches = new SolidPublishedSnapshot();
|
||||
var cache = caches.InnerContentCache;
|
||||
PopulateCache(factory, cache);
|
||||
return caches;
|
||||
}
|
||||
|
||||
internal abstract void PopulateCache(PublishedContentTypeFactory factory, SolidPublishedContentCache cache);
|
||||
}
|
||||
}
|
||||
@@ -34,11 +34,11 @@ namespace Umbraco.Tests.PublishedContent
|
||||
|
||||
Container.RegisterSingleton<IPublishedModelFactory>(f => new PublishedModelFactory(f.GetInstance<TypeLoader>().GetTypes<PublishedContentModel>()));
|
||||
Container.RegisterSingleton<IPublishedContentTypeFactory, PublishedContentTypeFactory>();
|
||||
Container.RegisterSingleton<IPublishedValueFallback, PublishedValueFallback>();
|
||||
Container.RegisterSingleton<IPublishedValueFallback, PublishedValueLanguageFallback>();
|
||||
|
||||
var logger = Mock.Of<ILogger>();
|
||||
var dataTypeService = new TestObjects.TestDataTypeService(
|
||||
new DataType(new VoidEditor(logger)) { Id = 1},
|
||||
new DataType(new VoidEditor(logger)) { Id = 1 },
|
||||
new DataType(new TrueFalsePropertyEditor(logger)) { Id = 1001 },
|
||||
new DataType(new RichTextPropertyEditor(logger)) { Id = 1002 },
|
||||
new DataType(new IntegerPropertyEditor(logger)) { Id = 1003 },
|
||||
@@ -333,7 +333,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetPropertyValueRecursiveTest()
|
||||
public void Get_Property_Value_Recursive()
|
||||
{
|
||||
var doc = GetNode(1174);
|
||||
var rVal = doc.Value("testRecursive", recurse: true);
|
||||
|
||||
@@ -257,10 +257,72 @@ namespace Umbraco.Tests.PublishedContent
|
||||
public bool SolidHasValue { get; set; }
|
||||
public object SolidXPathValue { get; set; }
|
||||
|
||||
public object GetSourceValue(string culture = null, string segment = null) => SolidSourceValue;
|
||||
public object GetValue(string culture = null, string segment = null) => SolidValue;
|
||||
public object GetXPathValue(string culture = null, string segment = null) => SolidXPathValue;
|
||||
public bool HasValue(string culture = null, string segment = null) => SolidHasValue;
|
||||
public virtual object GetSourceValue(string culture = null, string segment = null) => SolidSourceValue;
|
||||
public virtual object GetValue(string culture = null, string segment = null) => SolidValue;
|
||||
public virtual object GetXPathValue(string culture = null, string segment = null) => SolidXPathValue;
|
||||
public virtual bool HasValue(string culture = null, string segment = null) => SolidHasValue;
|
||||
}
|
||||
|
||||
internal class SolidPublishedPropertyWithLanguageVariants : SolidPublishedProperty
|
||||
{
|
||||
private readonly IDictionary<string, object> _solidSourceValues = new Dictionary<string, object>();
|
||||
private readonly IDictionary<string, object> _solidValues = new Dictionary<string, object>();
|
||||
private readonly IDictionary<string, object> _solidXPathValues = new Dictionary<string, object>();
|
||||
|
||||
public override object GetSourceValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.GetSourceValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidSourceValues.ContainsKey(culture) ? _solidSourceValues[culture] : null;
|
||||
}
|
||||
|
||||
public override object GetValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.GetValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidValues.ContainsKey(culture) ? _solidValues[culture] : null;
|
||||
}
|
||||
|
||||
public override object GetXPathValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.GetXPathValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidXPathValues.ContainsKey(culture) ? _solidXPathValues[culture] : null;
|
||||
}
|
||||
|
||||
public override bool HasValue(string culture = null, string segment = null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
return base.HasValue(culture, segment);
|
||||
}
|
||||
|
||||
return _solidSourceValues.ContainsKey(culture);
|
||||
}
|
||||
|
||||
public void SetSourceValue(string culture, object value)
|
||||
{
|
||||
_solidSourceValues.Add(culture, value);
|
||||
}
|
||||
|
||||
public void SetValue(string culture, object value)
|
||||
{
|
||||
_solidValues.Add(culture, value);
|
||||
}
|
||||
|
||||
public void SetXPathValue(string culture, object value)
|
||||
{
|
||||
_solidXPathValues.Add(culture, value);
|
||||
}
|
||||
}
|
||||
|
||||
[PublishedModel("ContentType2")]
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace Umbraco.Tests.TestHelpers
|
||||
{
|
||||
base.Compose();
|
||||
|
||||
Container.RegisterSingleton<IPublishedValueFallback, PublishedValueFallback>();
|
||||
Container.RegisterSingleton<IPublishedValueFallback, PublishedValueLanguageFallback>();
|
||||
Container.RegisterSingleton<ProfilingLogger>();
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,8 @@
|
||||
<Compile Include="Migrations\MigrationTests.cs" />
|
||||
<Compile Include="Models\PathValidationTests.cs" />
|
||||
<Compile Include="Models\VariationTests.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentLanuageVariantTests.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
|
||||
<Compile Include="PublishedContent\SolidPublishedSnapshot.cs" />
|
||||
<Compile Include="PublishedContent\NuCacheTests.cs" />
|
||||
<Compile Include="Testing\Objects\TestDataSource.cs" />
|
||||
|
||||
@@ -29,7 +29,10 @@
|
||||
"languages_mandatoryLanguageHelp",
|
||||
"languages_defaultLanguage",
|
||||
"languages_defaultLanguageHelp",
|
||||
"languages_addLanguage"
|
||||
"languages_addLanguage",
|
||||
"languages_noFallbackLanguageOption",
|
||||
"languages_fallbackLanguageDescription",
|
||||
"languages_fallbackLanguage"
|
||||
];
|
||||
|
||||
localizationService.localizeMany(labelKeys).then(function (values) {
|
||||
@@ -39,8 +42,17 @@
|
||||
vm.labels.defaultLanguage = values[3];
|
||||
vm.labels.defaultLanguageHelp = values[4];
|
||||
vm.labels.addLanguage = values[5];
|
||||
vm.labels.noFallbackLanguageOption = values[6];
|
||||
|
||||
if($routeParams.create) {
|
||||
$scope.properties = {
|
||||
fallbackLanguage: {
|
||||
alias: "fallbackLanguage",
|
||||
description: values[7],
|
||||
label: values[8]
|
||||
}
|
||||
};
|
||||
|
||||
if ($routeParams.create) {
|
||||
vm.page.name = vm.labels.addLanguage;
|
||||
languageResource.getCultures().then(function (culturesDictionary) {
|
||||
var cultures = [];
|
||||
@@ -53,10 +65,17 @@
|
||||
vm.availableCultures = cultures;
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
if(!$routeParams.create) {
|
||||
vm.loading = true;
|
||||
languageResource.getAll().then(function (languages) {
|
||||
vm.availableLanguages = languages.filter(function (l) {
|
||||
return $routeParams.id != l.id;
|
||||
});
|
||||
vm.loading = false;
|
||||
});
|
||||
|
||||
if (!$routeParams.create) {
|
||||
|
||||
vm.loading = true;
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
hide-alias="true">
|
||||
</umb-editor-header>
|
||||
|
||||
<umb-editor-container>
|
||||
<umb-editor-container class="form-horizontal">
|
||||
|
||||
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
|
||||
|
||||
<umb-box ng-if="!vm.loading" class="block-form">
|
||||
<umb-box ng-if="!vm.loading">
|
||||
<umb-box-content>
|
||||
|
||||
<umb-control-group label="@general_language" ng-if="vm.availableCultures">
|
||||
@@ -64,6 +64,16 @@
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
<umb-property property="properties.fallbackLanguage">
|
||||
<div>
|
||||
<select name="fallbackLanguage"
|
||||
ng-model="vm.language.fallbackLanguageId"
|
||||
ng-options="l.id as l.name for l in vm.availableLanguages">
|
||||
<option value="">{{vm.labels.noFallbackLanguageOption}}</option>
|
||||
</select>
|
||||
</div>
|
||||
</umb-property>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
|
||||
@@ -13,6 +13,16 @@
|
||||
vm.editLanguage = editLanguage;
|
||||
vm.deleteLanguage = deleteLanguage;
|
||||
|
||||
vm.getLanguageById = function(id) {
|
||||
for (var i = 0; i < vm.languages.length; i++) {
|
||||
if (vm.languages[i].id === id) {
|
||||
return vm.languages[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
function init() {
|
||||
|
||||
vm.loading = true;
|
||||
@@ -22,17 +32,19 @@
|
||||
"treeHeaders_languages",
|
||||
"general_mandatory",
|
||||
"general_default",
|
||||
"languages_fallsbackToLabel"
|
||||
];
|
||||
|
||||
localizationService.localizeMany(labelKeys).then(function (values) {
|
||||
vm.labels.languages = values[0];
|
||||
vm.labels.mandatory = values[1];
|
||||
vm.labels.general = values[2];
|
||||
vm.labels.fallsbackTo = values[3];
|
||||
// set page name
|
||||
vm.page.name = vm.labels.languages;
|
||||
});
|
||||
|
||||
languageResource.getAll().then(function(languages) {
|
||||
languageResource.getAll().then(function (languages) {
|
||||
vm.languages = languages;
|
||||
vm.loading = false;
|
||||
});
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<span ng-if="language.isDefault">- {{vm.labels.general}}</span>
|
||||
</td>
|
||||
<td><span ng-if="language.isMandatory">{{vm.labels.mandatory}}</span></td>
|
||||
<td><span ng-if="language.fallbackLanguageId">{{vm.labels.fallsbackTo}}: {{vm.getLanguageById(language.fallbackLanguageId).name}}</span></td>
|
||||
<td style="text-align: right;">
|
||||
<umb-button
|
||||
ng-if="!language.isDefault"
|
||||
|
||||
@@ -1691,10 +1691,14 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<area alias="languages">
|
||||
<key alias="addLanguage">Add language</key>
|
||||
<key alias="mandatoryLanguage">Mandatory</key>
|
||||
<key alias="mandatoryLanguageHelp">Properties on this language has to be filled out before the node can be published.</key>
|
||||
<key alias="mandatoryLanguageHelp">Properties on this language have to be filled out before the node can be published.</key>
|
||||
<key alias="defaultLanguage">Default language</key>
|
||||
<key alias="defaultLanguageHelp">An Umbraco site can only have one default langugae set.</key>
|
||||
<key alias="changingDefaultLanguageWarning">Switching default language may result in default content missing.</key>
|
||||
<key alias="fallsbackToLabel">Falls back to</key>
|
||||
<key alias="noFallbackLanguageOption">No fall back language</key>
|
||||
<key alias="fallbackLanguageDescription">To allow multi-lingual content to fall back to another language if not present in the requested language, select it here.</key>
|
||||
<key alias="fallbackLanguage">Fall back language</key>
|
||||
</area>
|
||||
|
||||
<area alias="modelsBuilder">
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@@ -8,8 +7,6 @@ using System.Web.Http;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Web.Models;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
@@ -69,12 +66,11 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
else if (allLangs.All(x => !x.IsDefaultVariantLanguage))
|
||||
{
|
||||
//if no language has the default flag, then the defaul language is the one with the lowest id
|
||||
//if no language has the default flag, then the default language is the one with the lowest id
|
||||
model.IsDefaultVariantLanguage = allLangs[0].Id == lang.Id;
|
||||
model.Mandatory = allLangs[0].Id == lang.Id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return model;
|
||||
}
|
||||
@@ -88,13 +84,23 @@ namespace Umbraco.Web.Editors
|
||||
public IHttpActionResult DeleteLanguage(int id)
|
||||
{
|
||||
var language = Services.LocalizationService.GetLanguageById(id);
|
||||
if (language == null) return NotFound();
|
||||
if (language == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var totalLangs = Services.LocalizationService.GetAllLanguages().Count();
|
||||
var langs = Services.LocalizationService.GetAllLanguages().ToArray();
|
||||
var totalLangs = langs.Length;
|
||||
|
||||
if (language.IsDefaultVariantLanguage || totalLangs == 1)
|
||||
{
|
||||
var message = $"Language '{language.IsoCode}' is currently set to 'default' or it is the only installed language and can not be deleted.";
|
||||
var message = $"Language '{language.CultureName}' is currently set to 'default' or it is the only installed language and cannot be deleted.";
|
||||
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(message));
|
||||
}
|
||||
|
||||
if (langs.Any(x => x.FallbackLanguageId.HasValue && x.FallbackLanguageId.Value == language.Id))
|
||||
{
|
||||
var message = $"Language '{language.CultureName}' is defined as a fall-back language for one or more other languages, and so cannot be deleted.";
|
||||
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(message));
|
||||
}
|
||||
|
||||
@@ -136,21 +142,62 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
|
||||
//create it
|
||||
var newLang = new Umbraco.Core.Models.Language(culture.Name)
|
||||
var newLang = new Core.Models.Language(culture.Name)
|
||||
{
|
||||
CultureName = culture.DisplayName,
|
||||
IsDefaultVariantLanguage = language.IsDefaultVariantLanguage,
|
||||
Mandatory = language.Mandatory
|
||||
Mandatory = language.Mandatory,
|
||||
FallbackLanguageId = language.FallbackLanguageId
|
||||
};
|
||||
|
||||
Services.LocalizationService.Save(newLang);
|
||||
return Mapper.Map<Language>(newLang);
|
||||
}
|
||||
|
||||
found.Mandatory = language.Mandatory;
|
||||
found.IsDefaultVariantLanguage = language.IsDefaultVariantLanguage;
|
||||
found.FallbackLanguageId = language.FallbackLanguageId;
|
||||
|
||||
string selectedFallbackLanguageCultureName;
|
||||
if (DoesUpdatedFallbackLanguageCreateACircularPath(found, out selectedFallbackLanguageCultureName))
|
||||
{
|
||||
ModelState.AddModelError("FallbackLanguage", "The selected fall back language '" + selectedFallbackLanguageCultureName + "' would create a circular path.");
|
||||
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
|
||||
}
|
||||
|
||||
Services.LocalizationService.Save(found);
|
||||
return Mapper.Map<Language>(found);
|
||||
}
|
||||
|
||||
|
||||
private bool DoesUpdatedFallbackLanguageCreateACircularPath(ILanguage language, out string selectedFallbackLanguageCultureName)
|
||||
{
|
||||
if (language.FallbackLanguageId.HasValue == false)
|
||||
{
|
||||
selectedFallbackLanguageCultureName = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
var languages = Services.LocalizationService.GetAllLanguages().ToArray();
|
||||
var fallbackLanguageId = language.FallbackLanguageId;
|
||||
while (fallbackLanguageId.HasValue)
|
||||
{
|
||||
if (fallbackLanguageId.Value == language.Id)
|
||||
{
|
||||
// We've found the current language in the path of fall back languages, so we have a circular path.
|
||||
selectedFallbackLanguageCultureName = GetLanguageFromCollectionById(languages, fallbackLanguageId.Value).CultureName;
|
||||
return true;
|
||||
}
|
||||
|
||||
fallbackLanguageId = GetLanguageFromCollectionById(languages, fallbackLanguageId.Value).FallbackLanguageId;
|
||||
}
|
||||
|
||||
selectedFallbackLanguageCultureName = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ILanguage GetLanguageFromCollectionById(IEnumerable<ILanguage> languages, int id)
|
||||
{
|
||||
return languages.Single(x => x.Id == id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,5 +24,8 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
|
||||
[DataMember(Name = "isMandatory")]
|
||||
public bool Mandatory { get; set; }
|
||||
|
||||
[DataMember(Name = "fallbackLanguageId")]
|
||||
public int? FallbackLanguageId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,45 +11,45 @@ namespace Umbraco.Web.Models.PublishedContent
|
||||
// kinda reproducing what was available in v7
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue)
|
||||
public virtual object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue)
|
||||
{
|
||||
// no fallback here
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue)
|
||||
public virtual T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue)
|
||||
{
|
||||
// no fallback here
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue)
|
||||
public virtual object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue)
|
||||
{
|
||||
// no fallback here
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue)
|
||||
public virtual T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue)
|
||||
{
|
||||
// no fallback here
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse)
|
||||
public virtual object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse, PublishedValueFallbackPriority fallbackPriority)
|
||||
{
|
||||
// no fallback here
|
||||
if (!recurse) return defaultValue;
|
||||
|
||||
// is that ok?
|
||||
return GetValue<object>(content, alias, culture, segment, defaultValue, recurse);
|
||||
return GetValue<object>(content, alias, culture, segment, defaultValue, true, fallbackPriority);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse)
|
||||
public virtual T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse, PublishedValueFallbackPriority fallbackPriority)
|
||||
{
|
||||
// no fallback here
|
||||
if (!recurse) return defaultValue;
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Web.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a default implementation for <see cref="IPublishedValueFallback"/> that allows
|
||||
/// for use of fall-back languages
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Inherits from <see cref="PublishedValueFallback" /> that implments what was available in v7.
|
||||
/// </remarks>
|
||||
public class PublishedValueLanguageFallback : PublishedValueFallback
|
||||
{
|
||||
private readonly ILocalizationService _localizationService;
|
||||
|
||||
public PublishedValueLanguageFallback(ServiceContext services)
|
||||
{
|
||||
_localizationService = services.LocalizationService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(IPublishedProperty property, string culture, string segment, object defaultValue)
|
||||
{
|
||||
object value;
|
||||
if (TryGetValueFromFallbackLanguage(property, culture, segment, defaultValue, out value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return base.GetValue(property, culture, segment, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T GetValue<T>(IPublishedProperty property, string culture, string segment, T defaultValue)
|
||||
{
|
||||
T value;
|
||||
if (TryGetValueFromFallbackLanguage(property, culture, segment, defaultValue, out value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return base.GetValue(property, culture, segment, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(IPublishedElement content, string alias, string culture, string segment, object defaultValue)
|
||||
{
|
||||
object value;
|
||||
if (TryGetValueFromFallbackLanguage(content, alias, culture, segment, defaultValue, out value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return base.GetValue(content, alias, culture, segment, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T GetValue<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue)
|
||||
{
|
||||
T value;
|
||||
if (TryGetValueFromFallbackLanguage(content, alias, culture, segment, defaultValue, out value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return base.GetValue(content, alias, culture, segment, defaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue(IPublishedContent content, string alias, string culture, string segment, object defaultValue, bool recurse, PublishedValueFallbackPriority fallbackPriority)
|
||||
{
|
||||
return GetValue<object>(content, alias, culture, segment, defaultValue, recurse, fallbackPriority);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T GetValue<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse, PublishedValueFallbackPriority fallbackPriority)
|
||||
{
|
||||
if (fallbackPriority == PublishedValueFallbackPriority.RecursiveTree)
|
||||
{
|
||||
var result = base.GetValue<T>(content, alias, culture, segment, defaultValue, recurse, PublishedValueFallbackPriority.RecursiveTree);
|
||||
if (ValueIsNotNullEmptyOrDefault(result, defaultValue))
|
||||
{
|
||||
// We've prioritised recursive tree search and found a value, so can return it.
|
||||
return result;
|
||||
}
|
||||
|
||||
if (TryGetValueFromFallbackLanguage(content, alias, culture, segment, defaultValue, recurse, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
if (fallbackPriority == PublishedValueFallbackPriority.FallbackLanguage)
|
||||
{
|
||||
T result;
|
||||
if (TryGetValueFromFallbackLanguage(content, alias, culture, segment, defaultValue, recurse, out result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// No language fall back content found, so use base implementation
|
||||
return base.GetValue<T>(content, alias, culture, segment, defaultValue, recurse, fallbackPriority);
|
||||
}
|
||||
|
||||
private bool TryGetValueFromFallbackLanguage<T>(IPublishedProperty property, string culture, string segment, T defaultValue, out T value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
var language = _localizationService.GetLanguageByIsoCode(culture);
|
||||
if (language.FallbackLanguageId.HasValue == false)
|
||||
{
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
var fallbackLanguageId = language.FallbackLanguageId;
|
||||
while (fallbackLanguageId.HasValue)
|
||||
{
|
||||
var fallbackLanguage = GetLanguageById(fallbackLanguageId.Value);
|
||||
value = property.Value(fallbackLanguage.IsoCode, segment, defaultValue);
|
||||
if (ValueIsNotNullEmptyOrDefault(value, defaultValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
fallbackLanguageId = fallbackLanguage.FallbackLanguageId;
|
||||
}
|
||||
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetValueFromFallbackLanguage<T>(IPublishedElement content, string alias, string culture, string segment, T defaultValue, out T value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
var language = _localizationService.GetLanguageByIsoCode(culture);
|
||||
if (language.FallbackLanguageId.HasValue == false)
|
||||
{
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
var fallbackLanguageId = language.FallbackLanguageId;
|
||||
while (fallbackLanguageId.HasValue)
|
||||
{
|
||||
var fallbackLanguage = GetLanguageById(fallbackLanguageId.Value);
|
||||
value = content.Value(alias, fallbackLanguage.IsoCode, segment, defaultValue);
|
||||
if (ValueIsNotNullEmptyOrDefault(value, defaultValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
fallbackLanguageId = fallbackLanguage.FallbackLanguageId;
|
||||
}
|
||||
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryGetValueFromFallbackLanguage<T>(IPublishedContent content, string alias, string culture, string segment, T defaultValue, bool recurse, out T value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(culture))
|
||||
{
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
var language = _localizationService.GetLanguageByIsoCode(culture);
|
||||
if (language.FallbackLanguageId.HasValue == false)
|
||||
{
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
var fallbackLanguageId = language.FallbackLanguageId;
|
||||
while (fallbackLanguageId.HasValue)
|
||||
{
|
||||
var fallbackLanguage = GetLanguageById(fallbackLanguageId.Value);
|
||||
value = content.Value(alias, fallbackLanguage.IsoCode, segment, defaultValue, recurse);
|
||||
if (ValueIsNotNullEmptyOrDefault(value, defaultValue))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
fallbackLanguageId = fallbackLanguage.FallbackLanguageId;
|
||||
}
|
||||
|
||||
value = defaultValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
private ILanguage GetLanguageById(int id)
|
||||
{
|
||||
return _localizationService.GetLanguageById(id);
|
||||
}
|
||||
|
||||
private static bool ValueIsNotNullEmptyOrDefault<T>(T value, T defaultValue)
|
||||
{
|
||||
return value != null &&
|
||||
string.IsNullOrEmpty(value.ToString()) == false &&
|
||||
value.Equals(defaultValue) == false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -152,8 +152,9 @@ namespace Umbraco.Web
|
||||
/// <param name="alias">The property alias.</param>
|
||||
/// <param name="culture">The variation language.</param>
|
||||
/// <param name="segment">The variation segment.</param>
|
||||
/// <param name="recurse">A value indicating whether to recurse.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <param name="recurse">A value indicating whether to recurse.</param>
|
||||
/// <param name="fallbackPriority">Flag indicating priority order of fallback paths in cases when content does not exist and a fall back method is used.</param>
|
||||
/// <returns>The value of the content's property identified by the alias, if it exists, otherwise a default value.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Recursively means: walking up the tree from <paramref name="content"/>, get the first value that can be found.</para>
|
||||
@@ -162,14 +163,14 @@ namespace Umbraco.Web
|
||||
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
|
||||
/// <para>The alias is case-insensitive.</para>
|
||||
/// </remarks>
|
||||
public static object Value(this IPublishedContent content, string alias, string culture = null, string segment = null, object defaultValue = default, bool recurse = false)
|
||||
public static object Value(this IPublishedContent content, string alias, string culture = null, string segment = null, object defaultValue = default, bool recurse = false, PublishedValueFallbackPriority fallbackPriority = PublishedValueFallbackPriority.RecursiveTree)
|
||||
{
|
||||
var property = content.GetProperty(alias);
|
||||
|
||||
if (property != null && property.HasValue(culture, segment))
|
||||
return property.GetValue(culture, segment);
|
||||
|
||||
return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue, recurse);
|
||||
return PublishedValueFallback.GetValue(content, alias, culture, segment, defaultValue, recurse, fallbackPriority);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -186,6 +187,7 @@ namespace Umbraco.Web
|
||||
/// <param name="segment">The variation segment.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <param name="recurse">A value indicating whether to recurse.</param>
|
||||
/// <param name="fallbackPriority">Flag indicating priority order of fallback paths in cases when content does not exist and a fall back method is used.</param>
|
||||
/// <returns>The value of the content's property identified by the alias, converted to the specified type.</returns>
|
||||
/// <remarks>
|
||||
/// <para>Recursively means: walking up the tree from <paramref name="content"/>, get the first value that can be found.</para>
|
||||
@@ -194,18 +196,18 @@ namespace Umbraco.Web
|
||||
/// <para>If eg a numeric property wants to default to 0 when value source is empty, this has to be done in the converter.</para>
|
||||
/// <para>The alias is case-insensitive.</para>
|
||||
/// </remarks>
|
||||
public static T Value<T>(this IPublishedContent content, string alias, string culture = null, string segment = null, T defaultValue = default, bool recurse = false)
|
||||
public static T Value<T>(this IPublishedContent content, string alias, string culture = null, string segment = null, T defaultValue = default, bool recurse = false, PublishedValueFallbackPriority fallbackPriority = PublishedValueFallbackPriority.RecursiveTree)
|
||||
{
|
||||
var property = content.GetProperty(alias);
|
||||
|
||||
if (property != null && property.HasValue(culture, segment))
|
||||
return property.Value<T>(culture, segment);
|
||||
|
||||
return PublishedValueFallback.GetValue<T>(content, alias, culture, segment, defaultValue, recurse);
|
||||
return PublishedValueFallback.GetValue<T>(content, alias, culture, segment, defaultValue, recurse, fallbackPriority);
|
||||
}
|
||||
|
||||
// fixme - .Value() refactoring - in progress
|
||||
public static IHtmlString Value<T>(this IPublishedContent content, string aliases, Func<T, string> format, string alt = "", bool recurse = false)
|
||||
public static IHtmlString Value<T>(this IPublishedContent content, string aliases, Func<T, string> format, string alt = "", bool recurse = false, PublishedValueFallbackPriority fallbackPriority = PublishedValueFallbackPriority.RecursiveTree)
|
||||
{
|
||||
var aliasesA = aliases.Split(',');
|
||||
if (aliasesA.Length == 0)
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace Umbraco.Web.Runtime
|
||||
composition.Container.Register(_ => GlobalHost.ConnectionManager.GetHubContext<PreviewHub>(), new PerContainerLifetime());
|
||||
|
||||
// register properties fallback
|
||||
composition.Container.RegisterSingleton<IPublishedValueFallback, PublishedValueFallback>();
|
||||
composition.Container.RegisterSingleton<IPublishedValueFallback, PublishedValueLanguageFallback>();
|
||||
}
|
||||
|
||||
internal void Initialize(
|
||||
|
||||
@@ -263,6 +263,7 @@
|
||||
<Compile Include="Models\Mapping\UserGroupDefaultPermissionsResolver.cs" />
|
||||
<Compile Include="Models\Mapping\ContentItemDisplayVariationResolver.cs" />
|
||||
<Compile Include="Models\PublishedContent\HttpContextVariationContextAccessor.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedValueLanguageFallback.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedValueFallback.cs" />
|
||||
<Compile Include="Models\RelatedLink.cs" />
|
||||
<Compile Include="Models\RelatedLinkBase.cs" />
|
||||
|
||||
Reference in New Issue
Block a user