From 89a701c86548f008db9e547a913efcdea7c7076d Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 9 Feb 2019 15:41:30 +0100 Subject: [PATCH] Initial PoC --- .../Implement/EntityRepository.cs | 21 ++++++++-- .../src/common/interceptors/_module.js | 2 + .../culturerequest.interceptor.js | 27 +++++++++++++ src/Umbraco.Web/Editors/EntityController.cs | 39 ++++++++++++------- .../Filters/HttpQueryStringFilterAttribute.cs | 34 +++++++++++++++- .../WebApi/HttpRequestMessageExtensions.cs | 7 ++++ 6 files changed, 112 insertions(+), 18 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 85912694f0..fc7f36b1a3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -66,8 +66,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //no matter what we always must have node id ordered at the end sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId"); - var page = Database.Page(pageIndex + 1, pageSize, sql); - var dtos = page.Items; + // for content we must query for ContentEntityDto entities to produce the correct culture variant entity names + var pageIndexToFetch = pageIndex + 1; + IEnumerable dtos; + if(isContent) + { + var page = Database.Page(pageIndexToFetch, pageSize, sql); + dtos = page.Items; + totalRecords = page.TotalItems; + } + else + { + var page = Database.Page(pageIndexToFetch, pageSize, sql); + dtos = page.Items; + totalRecords = page.TotalItems; + } + var entities = dtos.Select(x => BuildEntity(isContent, isMedia, x)).ToArray(); if (isContent) @@ -75,9 +89,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // TODO: see https://github.com/umbraco/Umbraco-CMS/pull/3460#issuecomment-434903930 we need to not load any property data at all for media if (isMedia) - BuildProperties(entities, dtos); + BuildProperties(entities, dtos.ToList()); - totalRecords = page.TotalItems; return entities; } diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js index 05263cebf2..69a4fe35c9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/_module.js @@ -7,4 +7,6 @@ angular.module('umbraco.interceptors', []) $httpProvider.interceptors.push('securityInterceptor'); $httpProvider.interceptors.push('debugRequestInterceptor'); $httpProvider.interceptors.push('doNotPostDollarVariablesOnPostRequestInterceptor'); + $httpProvider.interceptors.push('cultureRequestInterceptor'); + }]); diff --git a/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js b/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js new file mode 100644 index 0000000000..a0d827948b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/interceptors/culturerequest.interceptor.js @@ -0,0 +1,27 @@ +(function() { + 'use strict'; + + /** + * Used to set the current client culture on all requests API requests + * @param {any} $q + * @param {any} $routeParams + */ + function cultureRequestInterceptor($q, $routeParams) { + return { + //dealing with requests: + 'request': function(config) { + var apiPattern = /\/umbraco\/backoffice\//; + if (!apiPattern.test(config.url)) { + // it's not an API request, no handling + return config; + } + // it's an API request, add the current client culture as a header value + config.headers["X-UMB-CULTURE"] = $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture; + return config; + } + }; + } + + angular.module('umbraco.interceptors').factory('cultureRequestInterceptor', cultureRequestInterceptor); + +})(); diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 55604e0eb9..ad750c3b53 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -503,10 +503,14 @@ namespace Umbraco.Web.Editors return new PagedResult(0, 0, 0); } + var culture = Request.ClientCulture(); var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) { Items = entities.Select(entity => Mapper.Map(entity, options => - options.AfterMap((src, dest) => { dest.AdditionalData["hasChildren"] = src.HasChildren; }) + { + options.SetCulture(culture); + options.AfterMap((src, dest) => { dest.AdditionalData["hasChildren"] = src.HasChildren; }); + } ) ) }; @@ -585,7 +589,7 @@ namespace Umbraco.Web.Editors var pagedResult = new PagedResult(totalRecords, pageNumber, pageSize) { - Items = entities.Select(Mapper.Map) + Items = entities.Select(MapEntities()) }; return pagedResult; @@ -632,7 +636,7 @@ namespace Umbraco.Web.Editors return Services.EntityService.GetChildren(id, objectType.Value) .WhereNotNull() - .Select(Mapper.Map); + .Select(MapEntities()); } //now we need to convert the unknown ones switch (entityType) @@ -693,7 +697,7 @@ namespace Umbraco.Web.Editors : Services.EntityService.GetAll(objectType.Value, ids) .WhereNotNull() .OrderBy(x => x.Level) - .Select(x => Mapper.Map(x, opts => { opts.SetCulture(culture);})); + .Select(MapEntities(culture)); } //now we need to convert the unknown ones switch (entityType) @@ -719,7 +723,7 @@ namespace Umbraco.Web.Editors { var entities = Services.EntityService.GetAll(objectType.Value, keys) .WhereNotNull() - .Select(Mapper.Map); + .Select(MapEntities()); // entities are in "some" order, put them back in order var xref = entities.ToDictionary(x => x.Key); @@ -751,7 +755,7 @@ namespace Umbraco.Web.Editors { var entities = Services.EntityService.GetAll(objectType.Value, ids) .WhereNotNull() - .Select(Mapper.Map); + .Select(MapEntities()); // entities are in "some" order, put them back in order var xref = entities.ToDictionary(x => x.Id); @@ -815,7 +819,7 @@ namespace Umbraco.Web.Editors { throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map(found); + return MapEntity(found); } //now we need to convert the unknown ones switch (entityType) @@ -886,7 +890,7 @@ namespace Umbraco.Web.Editors if (objectType.HasValue) { // TODO: Should we order this by something ? - var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(Mapper.Map); + var entities = Services.EntityService.GetAll(objectType.Value).WhereNotNull().Select(MapEntities()); return ExecutePostFilter(entities, postFilter); } //now we need to convert the unknown ones @@ -895,13 +899,13 @@ namespace Umbraco.Web.Editors case UmbracoEntityTypes.Template: var templates = Services.FileService.GetTemplates(); var filteredTemplates = ExecutePostFilter(templates, postFilter); - return filteredTemplates.Select(Mapper.Map); + return filteredTemplates.Select(MapEntities()); case UmbracoEntityTypes.Macro: //Get all macros from the macro service var macros = Services.MacroService.GetAll().WhereNotNull().OrderBy(x => x.Name); var filteredMacros = ExecutePostFilter(macros, postFilter); - return filteredMacros.Select(Mapper.Map); + return filteredMacros.Select(MapEntities()); case UmbracoEntityTypes.PropertyType: @@ -936,14 +940,14 @@ namespace Umbraco.Web.Editors if (!postFilter.IsNullOrWhiteSpace()) throw new NotSupportedException("Filtering on stylesheets is not currently supported"); - return Services.FileService.GetStylesheets().Select(Mapper.Map); + return Services.FileService.GetStylesheets().Select(MapEntities()); case UmbracoEntityTypes.Language: if (!postFilter.IsNullOrWhiteSpace() ) throw new NotSupportedException("Filtering on languages is not currently supported"); - return Services.LocalizationService.GetAllLanguages().Select(Mapper.Map); + return Services.LocalizationService.GetAllLanguages().Select(MapEntities()); case UmbracoEntityTypes.DictionaryItem: if (!postFilter.IsNullOrWhiteSpace()) @@ -1039,8 +1043,17 @@ namespace Umbraco.Web.Editors return queryCondition; } + private Func MapEntities(string culture = null) + { + culture = culture ?? Request.ClientCulture(); + return x => MapEntity(x, culture); + } - + private EntityBasic MapEntity(object entity, string culture = null) + { + culture = culture ?? Request.ClientCulture(); + return Mapper.Map(entity, opts => { opts.SetCulture(culture); }); + } #region Methods to get all dictionary items private IEnumerable GetAllDictionaryItems() diff --git a/src/Umbraco.Web/WebApi/Filters/HttpQueryStringFilterAttribute.cs b/src/Umbraco.Web/WebApi/Filters/HttpQueryStringFilterAttribute.cs index eea4ef7e67..56f0eefe5c 100644 --- a/src/Umbraco.Web/WebApi/Filters/HttpQueryStringFilterAttribute.cs +++ b/src/Umbraco.Web/WebApi/Filters/HttpQueryStringFilterAttribute.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.Http.Formatting; using System.Web.Http.Controllers; using System.Web.Http.Filters; @@ -32,7 +33,14 @@ namespace Umbraco.Web.WebApi.Filters var queryStrings = actionContext.Request.Properties["MS_QueryNameValuePairs"] as IEnumerable>; if (queryStrings == null) return; - var formData = new FormDataCollection(queryStrings); + var queryStringKeys = queryStrings.Select(kvp => kvp.Key).ToArray(); + var additionalParameters = new Dictionary(); + + if(queryStringKeys.Contains("culture") == false) { + additionalParameters["culture"] = actionContext.Request.ClientCulture(); + } + + var formData = new FormDataCollection(queryStrings.Union(additionalParameters)); actionContext.ActionArguments[ParameterName] = formData; } @@ -40,4 +48,28 @@ namespace Umbraco.Web.WebApi.Filters base.OnActionExecuting(actionContext); } } + + public sealed class UnwrapClientCultureFilterAttribute : ActionFilterAttribute + { + private readonly string _parameterName; + + public UnwrapClientCultureFilterAttribute(string parameterName = "culture") + { + if (string.IsNullOrEmpty(parameterName)) + throw new ArgumentException("ParameterName is required."); + _parameterName = parameterName; + } + + public override void OnActionExecuting(HttpActionContext actionContext) + { + if (actionContext.ActionArguments.ContainsKey(_parameterName) && string.IsNullOrWhiteSpace(actionContext.ActionArguments[_parameterName]?.ToString()) == false) + { + return; + } + + actionContext.ActionArguments[_parameterName] = actionContext.Request.ClientCulture(); + + base.OnActionExecuting(actionContext); + } + } } diff --git a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs index 96c9c0e9a0..744b60cd52 100644 --- a/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs +++ b/src/Umbraco.Web/WebApi/HttpRequestMessageExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Globalization; +using System.Linq; using System.Net; using System.Net.Http; using System.Web; @@ -158,6 +160,11 @@ namespace Umbraco.Web.WebApi msg.Headers.Add("X-Status-Reason", "Validation failed"); return msg; } + + public static string ClientCulture(this HttpRequestMessage request) + { + return request.Headers.Contains("X-UMB-CULTURE") ? request.Headers.GetValues("X-UMB-CULTURE").First() : null; + } } }