diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/LanguageColumns.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/LanguageColumns.cs new file mode 100644 index 0000000000..18246ac14b --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/LanguageColumns.cs @@ -0,0 +1,15 @@ +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class LanguageColumns : MigrationBase + { + protected LanguageColumns(IMigrationContext context) : base(context) + { + } + + public override void Migrate() + { + Create.Column("isDefaultVariantLang").OnTable(Constants.DatabaseSchema.Tables.Language).AsBoolean().NotNullable(); + Create.Column("mandatory").OnTable(Constants.DatabaseSchema.Tables.Language).AsBoolean().NotNullable(); + } + } +} diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs index ba29880e79..3ea761016b 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/SuperZero.cs @@ -1,5 +1,6 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { + public class SuperZero : MigrationBase { public SuperZero(IMigrationContext context) diff --git a/src/Umbraco.Core/Models/ILanguage.cs b/src/Umbraco.Core/Models/ILanguage.cs index 8eb9063302..23cef54180 100644 --- a/src/Umbraco.Core/Models/ILanguage.cs +++ b/src/Umbraco.Core/Models/ILanguage.cs @@ -23,5 +23,15 @@ namespace Umbraco.Core.Models /// [IgnoreDataMember] CultureInfo CultureInfo { get; } + + /// + /// Defines if this language is the default variant language when language variants are in use + /// + bool IsDefaultVariantLanguage { get; set; } + + /// + /// If true, a variant node cannot be published unless this language variant is created + /// + bool Mandatory { get; set; } } } diff --git a/src/Umbraco.Core/Models/Language.cs b/src/Umbraco.Core/Models/Language.cs index 6dfe59778f..a143a8c457 100644 --- a/src/Umbraco.Core/Models/Language.cs +++ b/src/Umbraco.Core/Models/Language.cs @@ -17,6 +17,8 @@ namespace Umbraco.Core.Models private string _isoCode; private string _cultureName; + private bool _isDefaultVariantLanguage; + private bool _mandatory; public Language(string isoCode) { @@ -28,6 +30,8 @@ namespace Umbraco.Core.Models { public readonly PropertyInfo IsoCodeSelector = ExpressionHelper.GetPropertyInfo(x => x.IsoCode); public readonly PropertyInfo CultureNameSelector = ExpressionHelper.GetPropertyInfo(x => x.CultureName); + public readonly PropertyInfo IsDefaultVariantLanguageSelector = ExpressionHelper.GetPropertyInfo(x => x.IsDefaultVariantLanguage); + public readonly PropertyInfo MandatorySelector = ExpressionHelper.GetPropertyInfo(x => x.Mandatory); } /// @@ -55,5 +59,17 @@ namespace Umbraco.Core.Models /// [IgnoreDataMember] public CultureInfo CultureInfo => CultureInfo.GetCultureInfo(IsoCode); + + public bool IsDefaultVariantLanguage + { + get => _isDefaultVariantLanguage; + set => SetPropertyValueAndDetectChanges(value, ref _isDefaultVariantLanguage, Ps.Value.IsDefaultVariantLanguageSelector); + } + + public bool Mandatory + { + get => _mandatory; + set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector); + } } } diff --git a/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs b/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs index 83745a2bfe..f88d9fbb5c 100644 --- a/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs +++ b/src/Umbraco.Core/Persistence/Dtos/LanguageDto.cs @@ -22,5 +22,17 @@ namespace Umbraco.Core.Persistence.Dtos [NullSetting(NullSetting = NullSettings.Null)] [Length(100)] public string CultureName { get; set; } + + /// + /// Defines if this language is the default variant language when language variants are in use + /// + [Column("isDefaultVariantLang")] + public bool IsDefaultVariantLanguage { get; set; } + + /// + /// If true, a variant node cannot be published unless this language variant is created + /// + [Column("mandatory")] + public bool Mandatory { get; set; } } } diff --git a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs index f15313622b..f79dab1bbd 100644 --- a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs @@ -8,7 +8,7 @@ namespace Umbraco.Core.Persistence.Factories { public ILanguage BuildEntity(LanguageDto dto) { - var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id }; + var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id, IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage, Mandatory = dto.Mandatory }; // reset dirty initial properties (U4-1946) lang.ResetDirtyProperties(false); return lang; @@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence.Factories public LanguageDto BuildDto(ILanguage entity) { - var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode }; + var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode, IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage, Mandatory = entity.Mandatory }; if (entity.HasIdentity) dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index 784191279d..1397d79ea8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -107,6 +107,14 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { ((EntityBase)entity).AddingEntity(); + if (entity.IsDefaultVariantLanguage) + { + //if this entity is flagged as the default, we need to set all others to false + Database.Execute($"UPDATE {SqlSyntax.GetQuotedColumnName(Constants.DatabaseSchema.Tables.Language)} SET {SqlSyntax.GetQuotedColumnName("isDefaultVariantLang")} = 0"); + //We need to clear the whole cache since all languages will be updated + IsolatedCache.ClearAllCache(); + } + var factory = new LanguageFactory(); var dto = factory.BuildDto(entity); @@ -114,12 +122,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement entity.Id = id; entity.ResetDirtyProperties(); + } protected override void PersistUpdatedItem(ILanguage entity) { ((EntityBase)entity).UpdatingEntity(); + if (entity.IsDefaultVariantLanguage) + { + //if this entity is flagged as the default, we need to set all others to false + Database.Execute($"UPDATE {SqlSyntax.GetQuotedColumnName(Constants.DatabaseSchema.Tables.Language)} SET {SqlSyntax.GetQuotedColumnName("isDefaultVariantLang")} = 0"); + //We need to clear the whole cache since all languages will be updated + IsolatedCache.ClearAllCache(); + } + var factory = new LanguageFactory(); var dto = factory.BuildDto(entity); diff --git a/src/Umbraco.Core/Services/Implement/LocalizationService.cs b/src/Umbraco.Core/Services/Implement/LocalizationService.cs index 70103d9a06..1af053b787 100644 --- a/src/Umbraco.Core/Services/Implement/LocalizationService.cs +++ b/src/Umbraco.Core/Services/Implement/LocalizationService.cs @@ -411,7 +411,7 @@ namespace Umbraco.Core.Services.Implement } } - #region Event Handlers + #region Events /// /// Occurs before Delete /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 00f1a85d79..7089d164bc 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -315,6 +315,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs index 3d0b484a50..0033c2524b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/LanguageRepositoryTest.cs @@ -219,6 +219,59 @@ namespace Umbraco.Tests.Persistence.Repositories // Assert Assert.That(languageBR.HasIdentity, Is.True); Assert.That(languageBR.Id, Is.EqualTo(6)); //With 5 existing entries the Id should be 6 + Assert.IsFalse(languageBR.IsDefaultVariantLanguage); + Assert.IsFalse(languageBR.Mandatory); + } + } + + [Test] + public void Can_Perform_Add_On_LanguageRepository_With_Boolean_Properties() + { + // 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", IsDefaultVariantLanguage = true, Mandatory = true }; + 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.IsTrue(languageBR.IsDefaultVariantLanguage); + Assert.IsTrue(languageBR.Mandatory); + } + } + + [Test] + public void Can_Perform_Add_On_LanguageRepository_With_New_Deafult() + { + // Arrange + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider); + + var languageBR = (ILanguage)new Language("pt-BR") { CultureName = "pt-BR", IsDefaultVariantLanguage = true, Mandatory = true }; + repository.Save(languageBR); + var languageEN = new Language("en-AU") { CultureName = "en-AU" }; + repository.Save(languageEN); + + Assert.IsTrue(languageBR.IsDefaultVariantLanguage); + 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); } } diff --git a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs index b7f958cb8e..e39ebfee8e 100644 --- a/src/Umbraco.Tests/Services/LocalizationServiceTests.cs +++ b/src/Umbraco.Tests/Services/LocalizationServiceTests.cs @@ -364,6 +364,28 @@ namespace Umbraco.Tests.Services Assert.NotNull(result); } + [Test] + public void Set_Default_Language() + { + var localizationService = ServiceContext.LocalizationService; + var language = new Core.Models.Language("en-AU"); + language.IsDefaultVariantLanguage = true; + localizationService.Save(language); + var result = localizationService.GetLanguageById(language.Id); + + Assert.IsTrue(result.IsDefaultVariantLanguage); + + var language2 = new Core.Models.Language("en-NZ"); + language2.IsDefaultVariantLanguage = true; + localizationService.Save(language2); + var result2 = localizationService.GetLanguageById(language2.Id); + //re-get + result = localizationService.GetLanguageById(language.Id); + + Assert.IsTrue(result2.IsDefaultVariantLanguage); + Assert.IsFalse(result.IsDefaultVariantLanguage); + } + [Test] public void Deleted_Language_Should_Not_Exist() {