using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; using Language = Umbraco.Web.Models.ContentEditing.Language; namespace Umbraco.Web.Editors { /// /// Backoffice controller supporting the dashboard for language administration. /// [PluginController("UmbracoApi")] [PrefixlessBodyModelValidator] public class LanguageController : UmbracoAuthorizedJsonController { /// /// Returns all cultures available for creating languages. /// /// [HttpGet] public IDictionary GetAllCultures() { return CultureInfo.GetCultures(CultureTypes.AllCultures) .Where(x => !x.Name.IsNullOrWhiteSpace()) .OrderBy(x => x.DisplayName).ToDictionary(x => x.Name, x => x.DisplayName); } /// /// Returns all currently configured languages. /// /// [HttpGet] public IEnumerable GetAllLanguages() { var allLanguages = Services.LocalizationService.GetAllLanguages(); return Mapper.Map, IEnumerable>(allLanguages); } [HttpGet] public Language GetLanguage(int id) { var lang = Services.LocalizationService.GetLanguageById(id); if (lang == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); var model = Mapper.Map(lang); //if there's only one language, by default it is the default var allLangs = Services.LocalizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); if (!lang.IsDefaultVariantLanguage) { if (allLangs.Count == 1) { model.IsDefaultVariantLanguage = true; model.Mandatory = true; } 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 model.IsDefaultVariantLanguage = allLangs[0].Id == lang.Id; model.Mandatory = allLangs[0].Id == lang.Id; } } return model; } /// /// Deletes a language with a given ID /// [UmbracoTreeAuthorize(Core.Constants.Trees.Languages)] [HttpDelete] [HttpPost] public IHttpActionResult DeleteLanguage(int id) { var language = Services.LocalizationService.GetLanguageById(id); if (language == null) return NotFound(); var totalLangs = Services.LocalizationService.GetAllLanguages().Count(); 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."; throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(message)); } Services.LocalizationService.Delete(language); return Ok(); } /// /// Creates or saves a language /// [UmbracoTreeAuthorize(Core.Constants.Trees.Languages)] [HttpPost] public Language SaveLanguage(Language language) { if (!ModelState.IsValid) throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); var found = Services.LocalizationService.GetLanguageByIsoCode(language.IsoCode); if (found != null && language.Id != found.Id) { //someone is trying to create a language that alraedy exist ModelState.AddModelError("IsoCode", "The language " + language.IsoCode + " already exists"); throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } if (found == null) { CultureInfo culture; try { culture = CultureInfo.GetCultureInfo(language.IsoCode); } catch (CultureNotFoundException) { ModelState.AddModelError("IsoCode", "No Culture found with name " + language.IsoCode); throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } //create it var newLang = new Core.Models.Language(culture.Name) { CultureName = culture.DisplayName, IsDefaultVariantLanguage = language.IsDefaultVariantLanguage, Mandatory = language.Mandatory, }; AssociateFallbackLanguage(language, newLang); Services.LocalizationService.Save(newLang); return Mapper.Map(newLang); } found.Mandatory = language.Mandatory; found.IsDefaultVariantLanguage = language.IsDefaultVariantLanguage; AssociateFallbackLanguage(language, found); if (UpdatedFallbackLanguageCreatesCircularPath(found)) { ModelState.AddModelError("FallbackLanguage", "The selected fall back language '" + found.FallbackLanguage.CultureName + "' would create a circular path."); throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } Services.LocalizationService.Save(found); return Mapper.Map(found); } private static void AssociateFallbackLanguage(Language submittedLanguage, ILanguage languageToCreateOrUpdate) { 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 UpdatedFallbackLanguageCreatesCircularPath(ILanguage language) { if (language.FallbackLanguage == null) { return false; } var languages = Services.LocalizationService.GetAllLanguages().ToArray(); var fallbackLanguage = language.FallbackLanguage; while (fallbackLanguage != null) { if (fallbackLanguage.Id == language.Id) { return true; } fallbackLanguage = languages.Single(x => x.Id == fallbackLanguage.Id).FallbackLanguage; } return false; } } }