Refactored to object graph reference of fallback language to just use id

This commit is contained in:
AndyButland
2018-07-12 20:52:02 +01:00
parent d1a31ad8f8
commit 91a0ee2c93
13 changed files with 80 additions and 115 deletions

View File

@@ -35,9 +35,9 @@ namespace Umbraco.Core.Models
bool Mandatory { get; set; }
/// <summary>
/// Defines the fallback language that can be used in multi-lingual scenarios to provide
/// 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>
ILanguage FallbackLanguage { get; set; }
int? FallbackLanguageId { get; set; }
}
}

View File

@@ -19,7 +19,7 @@ namespace Umbraco.Core.Models
private string _cultureName;
private bool _isDefaultVariantLanguage;
private bool _mandatory;
private ILanguage _fallbackLanguage;
private int? _fallbackLanguageId;
public Language(string isoCode)
{
@@ -33,7 +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, ILanguage>(x => x.FallbackLanguage);
public readonly PropertyInfo FallbackLanguageSelector = ExpressionHelper.GetPropertyInfo<Language, int?>(x => x.FallbackLanguageId);
}
/// <summary>
@@ -74,10 +74,10 @@ namespace Umbraco.Core.Models
set => SetPropertyValueAndDetectChanges(value, ref _mandatory, Ps.Value.MandatorySelector);
}
public ILanguage FallbackLanguage
public int? FallbackLanguageId
{
get => _fallbackLanguage;
set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguage, Ps.Value.FallbackLanguageSelector);
get => _fallbackLanguageId;
set => SetPropertyValueAndDetectChanges(value, ref _fallbackLanguageId, Ps.Value.FallbackLanguageSelector);
}
}
}

View File

@@ -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,17 +16,12 @@ 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));
}
if (entity.FallbackLanguage != null)
{
dto.FallbackLanguageId = entity.FallbackLanguage.Id;
}
return dto;
}
}

View File

@@ -54,7 +54,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// get languages
var dtos = Database.Fetch<LanguageDto>(sql);
var languages = dtos.Select(ConvertFromDto).ToList();
PopulateFallbackLanguages(dtos, languages);
// initialize the code-id map
lock (_codeIdMap)
@@ -77,9 +76,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
var translator = new SqlTranslator<ILanguage>(sqlClause, query);
var sql = translator.Translate();
var dtos = Database.Fetch<LanguageDto>(sql);
var languages = dtos.Select(ConvertFromDto).ToList();
PopulateFallbackLanguages(dtos, languages);
return languages;
return dtos.Select(ConvertFromDto).ToList();
}
#endregion
@@ -203,16 +200,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return entity;
}
private static void PopulateFallbackLanguages(List<LanguageDto> dtos, IList<ILanguage> languages)
{
foreach (var dto in dtos.Where(x => x.FallbackLanguageId.HasValue))
{
var language = languages.Single(x => x.Id == dto.Id);
// ReSharper disable once PossibleInvalidOperationException (DTOs with fallback languages have already been filtered in the loop condition)
language.FallbackLanguage = languages.Single(x => x.Id == dto.FallbackLanguageId.Value);
}
}
public ILanguage GetByIsoCode(string isoCode)
{
TypedCachePolicy.GetAllCached(PerformGetAll); // ensure cache is populated, in a non-expensive way

View File

@@ -47,7 +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.FallbackLanguage, Is.Null);
Assert.That(language.FallbackLanguageId, Is.Null);
}
}
@@ -63,7 +63,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var language = (ILanguage)new Language(au.Name)
{
CultureName = au.DisplayName,
FallbackLanguage = repository.Get(1)
FallbackLanguageId = 1
};
repository.Save(language);
@@ -75,7 +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.FallbackLanguage.IsoCode, Is.EqualTo("en-US"));
Assert.That(language.FallbackLanguageId, Is.EqualTo(1));
}
}
@@ -193,7 +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.FallbackLanguage);
Assert.IsNull(languageBR.FallbackLanguageId);
}
}
@@ -215,7 +215,7 @@ 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.FallbackLanguage);
Assert.IsNull(languageBR.FallbackLanguageId);
}
}
@@ -232,14 +232,14 @@ namespace Umbraco.Tests.Persistence.Repositories
var languageBR = new Language("pt-BR")
{
CultureName = "pt-BR",
FallbackLanguage = repository.Get(1)
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.FallbackLanguage.IsoCode, Is.EqualTo("en-US"));
Assert.That(languageBR.FallbackLanguageId, Is.EqualTo(1));
}
}
@@ -284,7 +284,7 @@ namespace Umbraco.Tests.Persistence.Repositories
var language = repository.Get(5);
language.IsoCode = "pt-BR";
language.CultureName = "pt-BR";
language.FallbackLanguage = repository.Get(1);
language.FallbackLanguageId = 1;
repository.Save(language);
@@ -294,7 +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.FallbackLanguage.IsoCode, Is.EqualTo("en-US"));
Assert.That(languageUpdated.FallbackLanguageId, Is.EqualTo(1));
}
}

View File

@@ -39,15 +39,15 @@ namespace Umbraco.Tests.PublishedContent
{
new Language("en-US") { Id = 1, CultureName = "English", IsDefaultVariantLanguage = true },
new Language("fr") { Id = 2, CultureName = "French" },
new Language("es") { Id = 3, CultureName = "Spanish" },
new Language("it") { Id = 4, CultureName = "Italian" },
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" }
};
languages[2].FallbackLanguage = languages[0];
languages[3].FallbackLanguage = languages[2];
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));
}

View File

@@ -98,31 +98,11 @@
});
}
function setCultureForFallbackLanguage(lang) {
for (var i = 0; i < vm.availableLanguages.length; i++) {
if (vm.availableLanguages[i].id === lang.id) {
lang.culture = vm.availableLanguages[i].culture;
break;
}
}
}
function save() {
if (formHelper.submitForm({ scope: $scope })) {
vm.page.saveButtonState = "busy";
// Handle selection of no fall-back language (should pass null)
if (!vm.language.fallbackLanguage.id) {
vm.language.fallbackLanguage = null;
}
// We need to attach the ISO code to the fall-back language to pass
// server-side validation.
if (vm.language.fallbackLanguage) {
setCultureForFallbackLanguage(vm.language.fallbackLanguage);
}
languageResource.save(vm.language).then(function (lang) {
formHelper.resetForm({ scope: $scope });

View File

@@ -67,7 +67,7 @@
<umb-property property="properties.fallbackLanguage">
<div>
<select name="fallbackLanguage"
ng-model="vm.language.fallbackLanguage.id"
ng-model="vm.language.fallbackLanguageId"
ng-options="l.id as l.name for l in vm.availableLanguages">
<option value="">{{vm.labels.noFallbackLanguageOption}}</option>
</select>

View File

@@ -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;

View File

@@ -36,7 +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.fallbackLanguage">{{vm.labels.fallsbackTo}}: {{language.fallbackLanguage.name}}</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"

View File

@@ -98,7 +98,7 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(message));
}
if (language.FallbackLanguage != null && langs.Any(x => x.FallbackLanguage?.Id == language.Id))
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));
@@ -147,20 +147,21 @@ namespace Umbraco.Web.Editors
CultureName = culture.DisplayName,
IsDefaultVariantLanguage = language.IsDefaultVariantLanguage,
Mandatory = language.Mandatory,
FallbackLanguageId = language.FallbackLanguageId
};
AssociateFallbackLanguage(language, newLang);
Services.LocalizationService.Save(newLang);
return Mapper.Map<Language>(newLang);
}
found.Mandatory = language.Mandatory;
found.IsDefaultVariantLanguage = language.IsDefaultVariantLanguage;
AssociateFallbackLanguage(language, found);
found.FallbackLanguageId = language.FallbackLanguageId;
if (DoesUpdatedFallbackLanguageCreateACircularPath(found))
string selectedFallbackLanguageCultureName;
if (DoesUpdatedFallbackLanguageCreateACircularPath(found, out selectedFallbackLanguageCultureName))
{
ModelState.AddModelError("FallbackLanguage", "The selected fall back language '" + found.FallbackLanguage.CultureName + "' would create a circular path.");
ModelState.AddModelError("FallbackLanguage", "The selected fall back language '" + selectedFallbackLanguageCultureName + "' would create a circular path.");
throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState));
}
@@ -168,43 +169,35 @@ namespace Umbraco.Web.Editors
return Mapper.Map<Language>(found);
}
private static void AssociateFallbackLanguage(Language submittedLanguage, ILanguage languageToCreateOrUpdate)
private bool DoesUpdatedFallbackLanguageCreateACircularPath(ILanguage language, out string selectedFallbackLanguageCultureName)
{
if (submittedLanguage.FallbackLanguage == null)
{
languageToCreateOrUpdate.FallbackLanguage = null;
return;
}
var fallbackLanguageCulture = CultureInfo.GetCultureInfo(submittedLanguage.FallbackLanguage.IsoCode);
languageToCreateOrUpdate.FallbackLanguage = new Core.Models.Language(fallbackLanguageCulture.Name)
{
Id = submittedLanguage.FallbackLanguage.Id,
CultureName = fallbackLanguageCulture.DisplayName
};
}
private bool DoesUpdatedFallbackLanguageCreateACircularPath(ILanguage language)
{
if (language.FallbackLanguage == null)
if (language.FallbackLanguageId.HasValue == false)
{
selectedFallbackLanguageCultureName = string.Empty;
return false;
}
var languages = Services.LocalizationService.GetAllLanguages().ToArray();
var fallbackLanguage = language.FallbackLanguage;
while (fallbackLanguage != null)
var fallbackLanguageId = language.FallbackLanguageId;
while (fallbackLanguageId.HasValue)
{
if (fallbackLanguage.Id == language.Id)
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;
}
fallbackLanguage = languages.Single(x => x.Id == fallbackLanguage.Id).FallbackLanguage;
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);
}
}
}

View File

@@ -25,7 +25,7 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "isMandatory")]
public bool Mandatory { get; set; }
[DataMember(Name = "fallbackLanguage")]
public Language FallbackLanguage { get; set; }
[DataMember(Name = "fallbackLanguageId")]
public int? FallbackLanguageId { get; set; }
}
}

View File

@@ -107,13 +107,6 @@ namespace Umbraco.Web.Models.PublishedContent
return base.GetValue<T>(content, alias, culture, segment, defaultValue, recurse, fallbackPriority);
}
private static bool ValueIsNotNullEmptyOrDefault<T>(T value, T defaultValue)
{
return value != null &&
string.IsNullOrEmpty(value.ToString()) == false &&
value.Equals(defaultValue) == false;
}
private bool TryGetValueFromFallbackLanguage<T>(IPublishedProperty property, string culture, string segment, T defaultValue, out T value)
{
if (string.IsNullOrEmpty(culture))
@@ -123,22 +116,23 @@ namespace Umbraco.Web.Models.PublishedContent
}
var language = _localizationService.GetLanguageByIsoCode(culture);
if (language.FallbackLanguage == null)
if (language.FallbackLanguageId.HasValue == false)
{
value = defaultValue;
return false;
}
var fallbackLanguage = language.FallbackLanguage;
while (fallbackLanguage != null)
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;
}
fallbackLanguage = GetNextFallbackLanguage(fallbackLanguage);
fallbackLanguageId = fallbackLanguage.FallbackLanguageId;
}
value = defaultValue;
@@ -154,22 +148,23 @@ namespace Umbraco.Web.Models.PublishedContent
}
var language = _localizationService.GetLanguageByIsoCode(culture);
if (language.FallbackLanguage == null)
if (language.FallbackLanguageId.HasValue == false)
{
value = defaultValue;
return false;
}
var fallbackLanguage = language.FallbackLanguage;
while (fallbackLanguage != null)
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;
}
fallbackLanguage = GetNextFallbackLanguage(fallbackLanguage);
fallbackLanguageId = fallbackLanguage.FallbackLanguageId;
}
value = defaultValue;
@@ -185,34 +180,39 @@ namespace Umbraco.Web.Models.PublishedContent
}
var language = _localizationService.GetLanguageByIsoCode(culture);
if (language.FallbackLanguage == null)
if (language.FallbackLanguageId.HasValue == false)
{
value = defaultValue;
return false;
}
var fallbackLanguage = language.FallbackLanguage;
while (fallbackLanguage != null)
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;
}
fallbackLanguage = GetNextFallbackLanguage(fallbackLanguage);
fallbackLanguageId = fallbackLanguage.FallbackLanguageId;
}
value = defaultValue;
return false;
}
private ILanguage GetNextFallbackLanguage(ILanguage fallbackLanguage)
private ILanguage GetLanguageById(int id)
{
// Ensure reference to next fall-back language is loaded if it exists
fallbackLanguage = _localizationService.GetLanguageById(fallbackLanguage.Id);
return _localizationService.GetLanguageById(id);
}
return fallbackLanguage.FallbackLanguage;
private static bool ValueIsNotNullEmptyOrDefault<T>(T value, T defaultValue)
{
return value != null &&
string.IsNullOrEmpty(value.ToString()) == false &&
value.Equals(defaultValue) == false;
}
}
}