Fixes: U4-6307 Incorrect culture assigned to user (missing region code)

This commit is contained in:
Shannon
2015-03-06 16:01:49 +11:00
parent 7e9261d4bc
commit 0932c980e9
4 changed files with 146 additions and 21 deletions

View File

@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.Linq;
using System.Threading;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
@@ -23,13 +24,21 @@ namespace Umbraco.Core.Models
internal static CultureInfo GetUserCulture(string userLanguage, ILocalizedTextService textService)
{
return textService.GetSupportedCultures()
.FirstOrDefault(culture =>
//match on full name first
culture.Name.InvariantEquals(userLanguage.Replace("_", "-")) ||
//then match on the 2 letter name
culture.TwoLetterISOLanguageName.InvariantEquals(userLanguage));
}
try
{
var culture = CultureInfo.GetCultureInfo(userLanguage);
//TODO: This is a hack because we store the user language as 2 chars instead of the full culture
// which is actually stored in the language files (which are also named with 2 chars!) so we need to attempt
// to convert to a supported full culture
var result = textService.ConvertToSupportedCultureWithRegionCode(culture);
return result;
}
catch (CultureNotFoundException)
{
//return the default one
return CultureInfo.GetCultureInfo("en");
}
}
/// <summary>
/// Checks if the user has access to the content item based on their start noe

View File

@@ -33,5 +33,19 @@ namespace Umbraco.Core.Services
/// </summary>
/// <returns></returns>
IEnumerable<CultureInfo> GetSupportedCultures();
/// <summary>
/// Tries to resolve a full 4 letter culture from a 2 letter culture name
/// </summary>
/// <param name="currentCulture">
/// The culture to determine if it is only a 2 letter culture, if so we'll try to convert it, otherwise it will just be returned
/// </param>
/// <returns></returns>
/// <remarks>
/// TODO: This is just a hack due to the way we store the language files, they should be stored with 4 letters since that
/// is what they reference but they are stored with 2, further more our user's languages are stored with 2. So this attempts
/// to resolve the full culture if possible.
/// </remarks>
CultureInfo ConvertToSupportedCultureWithRegionCode(CultureInfo currentCulture);
}
}

View File

@@ -13,6 +13,7 @@ namespace Umbraco.Core.Services
public class LocalizedTextService : ILocalizedTextService
{
private readonly LocalizedTextServiceFileSources _fileSources;
private readonly IDictionary<CultureInfo, IDictionary<string, IDictionary<string, string>>> _dictionarySource;
private readonly IDictionary<CultureInfo, Lazy<XDocument>> _xmlSource;
@@ -22,7 +23,8 @@ namespace Umbraco.Core.Services
/// <param name="fileSources"></param>
public LocalizedTextService(LocalizedTextServiceFileSources fileSources)
{
_xmlSource = fileSources.GetXmlSources();
if (fileSources == null) throw new ArgumentNullException("fileSources");
_fileSources = fileSources;
}
/// <summary>
@@ -49,6 +51,9 @@ namespace Umbraco.Core.Services
{
Mandate.ParameterNotNull(culture, "culture");
//TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
culture = ConvertToSupportedCultureWithRegionCode(culture);
//This is what the legacy ui service did
if (string.IsNullOrEmpty(key))
return string.Empty;
@@ -57,10 +62,14 @@ namespace Umbraco.Core.Services
var area = keyParts.Length > 1 ? keyParts[0] : null;
var alias = keyParts.Length > 1 ? keyParts[1] : keyParts[0];
if (_xmlSource != null)
var xmlSource = _xmlSource ?? (_fileSources != null
? _fileSources.GetXmlSources()
: null);
if (xmlSource != null)
{
return GetFromXmlSource(culture, area, alias, tokens);
}
return GetFromXmlSource(xmlSource, culture, area, alias, tokens);
}
else
{
return GetFromDictionarySource(culture, area, alias, tokens);
@@ -75,18 +84,25 @@ namespace Umbraco.Core.Services
{
if (culture == null) throw new ArgumentNullException("culture");
//TODO: Hack, see notes on ConvertToSupportedCultureWithRegionCode
culture = ConvertToSupportedCultureWithRegionCode(culture);
var result = new Dictionary<string, string>();
if (_xmlSource != null)
var xmlSource = _xmlSource ?? (_fileSources != null
? _fileSources.GetXmlSources()
: null);
if (xmlSource != null)
{
if (_xmlSource.ContainsKey(culture) == false)
if (xmlSource.ContainsKey(culture) == false)
{
LogHelper.Warn<LocalizedTextService>("The culture specified {0} was not found in any configured sources for this service", () => culture);
return result;
}
//convert all areas + keys to a single key with a '/'
var areas = _xmlSource[culture].Value.XPathSelectElements("//area");
var areas = xmlSource[culture].Value.XPathSelectElements("//area");
foreach (var area in areas)
{
var keys = area.XPathSelectElements("./key");
@@ -133,7 +149,36 @@ namespace Umbraco.Core.Services
/// <returns></returns>
public IEnumerable<CultureInfo> GetSupportedCultures()
{
return _xmlSource != null ? _xmlSource.Keys : _dictionarySource.Keys;
var xmlSource = _xmlSource ?? (_fileSources != null
? _fileSources.GetXmlSources()
: null);
return xmlSource != null ? xmlSource.Keys : _dictionarySource.Keys;
}
/// <summary>
/// Tries to resolve a full 4 letter culture from a 2 letter culture name
/// </summary>
/// <param name="currentCulture">
/// The culture to determine if it is only a 2 letter culture, if so we'll try to convert it, otherwise it will just be returned
/// </param>
/// <returns></returns>
/// <remarks>
/// TODO: This is just a hack due to the way we store the language files, they should be stored with 4 letters since that
/// is what they reference but they are stored with 2, further more our user's languages are stored with 2. So this attempts
/// to resolve the full culture if possible.
///
/// This only works when this service is constructed with the LocalizedTextServiceFileSources
/// </remarks>
public CultureInfo ConvertToSupportedCultureWithRegionCode(CultureInfo currentCulture)
{
if (currentCulture == null) throw new ArgumentNullException("currentCulture");
if (_fileSources == null) return currentCulture;
if (currentCulture.Name.Length > 2) return currentCulture;
var attempt = _fileSources.TryConvert2LetterCultureTo4Letter(currentCulture.TwoLetterISOLanguageName);
return attempt ? attempt.Result : currentCulture;
}
private string GetFromDictionarySource(CultureInfo culture, string area, string key, IDictionary<string, string> tokens)
@@ -174,15 +219,15 @@ namespace Umbraco.Core.Services
return "[" + key + "]";
}
private string GetFromXmlSource(CultureInfo culture, string area, string key, IDictionary<string, string> tokens)
private static string GetFromXmlSource(IDictionary<CultureInfo, Lazy<XDocument>> xmlSource, CultureInfo culture, string area, string key, IDictionary<string, string> tokens)
{
if (_xmlSource.ContainsKey(culture) == false)
if (xmlSource.ContainsKey(culture) == false)
{
LogHelper.Warn<LocalizedTextService>("The culture specified {0} was not found in any configured sources for this service", () => culture);
return "[" + key + "]";
}
var cultureSource = _xmlSource[culture].Value;
var cultureSource = xmlSource[culture].Value;
var xpath = area.IsNullOrWhiteSpace()
? string.Format("//key [@alias = '{0}']", key)
@@ -213,7 +258,7 @@ namespace Umbraco.Core.Services
/// we support a dictionary which means in the future we can really have any sort of token system.
/// Currently though, the token key's will need to be an integer and sequential - though we aren't going to throw exceptions if that is not the case.
/// </remarks>
internal string ParseTokens(string value, IDictionary<string, string> tokens)
internal static string ParseTokens(string value, IDictionary<string, string> tokens)
{
if (tokens == null || tokens.Any() == false)
{

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Xml;
using System.Xml.Linq;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
@@ -16,6 +17,9 @@ namespace Umbraco.Core.Services
private readonly IRuntimeCacheProvider _cache;
private readonly DirectoryInfo _fileSourceFolder;
//TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
private readonly Dictionary<string, CultureInfo> _twoLetterCultureConverter = new Dictionary<string, CultureInfo>();
public LocalizedTextServiceFileSources(IRuntimeCacheProvider cache, DirectoryInfo fileSourceFolder)
{
if (cache == null) throw new ArgumentNullException("cache");
@@ -46,10 +50,53 @@ namespace Umbraco.Core.Services
{
var localCopy = fileInfo;
var filename = Path.GetFileNameWithoutExtension(localCopy.FullName).Replace("_", "-");
var culture = CultureInfo.GetCultureInfo(filename);
//TODO: Fix this nonsense... would have to wait until v8 to store the language files with their correct
// names instead of storing them as 2 letters but actually having a 4 letter culture. wtf. So now, we
// need to check if the file is 2 letters, then open it to try to find it's 4 letter culture, then use that
// if it's successful. We're going to assume (though it seems assuming in the legacy logic is never a great idea)
// that any 4 letter file is named with the actual culture that it is!
CultureInfo culture = null;
if (filename.Length == 2)
{
//we need to open the file to see if we can read it's 'real' culture, we'll use XmlReader since we don't
//want to load in the entire doc into mem just to read a single value
using (var fs = fileInfo.OpenRead())
using (var reader = XmlReader.Create(fs))
{
if (reader.IsStartElement())
{
if (reader.Name == "language")
{
if (reader.MoveToAttribute("culture"))
{
var cultureVal = reader.Value;
try
{
culture = CultureInfo.GetCultureInfo(cultureVal);
//add to the tracked dictionary
_twoLetterCultureConverter[filename] = culture;
}
catch (CultureNotFoundException)
{
LogHelper.Warn<LocalizedTextServiceFileSources>(
string.Format("The culture {0} found in the file {1} is not a valid culture", cultureVal, fileInfo.FullName));
//If the culture in the file is invalid, we'll just hope the file name is a valid culture below, otherwise
// an exception will be thrown.
}
}
}
}
}
}
if (culture == null)
{
culture = CultureInfo.GetCultureInfo(filename);
}
//get the lazy value from cache
result.Add(culture, new Lazy<XDocument>(() => _cache.GetCacheItem<XDocument>(
string.Format("{0}-{1}", typeof (LocalizedTextServiceFileSources).Name, culture.TwoLetterISOLanguageName), () =>
string.Format("{0}-{1}", typeof (LocalizedTextServiceFileSources).Name, culture.Name), () =>
{
using (var fs = localCopy.OpenRead())
{
@@ -59,5 +106,15 @@ namespace Umbraco.Core.Services
}
return result;
}
//TODO: See other notes in this class, this is purely a hack because we store 2 letter culture file names that contain 4 letter cultures :(
public Attempt<CultureInfo> TryConvert2LetterCultureTo4Letter(string twoLetterCulture)
{
if (twoLetterCulture.Length != 2) Attempt<CultureInfo>.Fail();
return _twoLetterCultureConverter.ContainsKey(twoLetterCulture)
? Attempt.Succeed(_twoLetterCultureConverter[twoLetterCulture])
: Attempt<CultureInfo>.Fail();
}
}
}