V10: fix build warnings in Web.BackOffice (#12479)
* Run code cleanup * Start manual run * Finish dotnet format + manual cleanup * Fix up after merge * Fix substrings changed to [..] Co-authored-by: Nikolaj Geisle <niko737@edu.ucl.dk> Co-authored-by: Zeegaan <nge@umbraco.dk>
This commit is contained in:
@@ -1,152 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Authorization.Policy;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Umbraco.Cms.Web.BackOffice.Extensions
|
||||
namespace Umbraco.Cms.Web.BackOffice.Extensions;
|
||||
|
||||
internal static class ControllerContextExtensions
|
||||
{
|
||||
internal static class ControllerContextExtensions
|
||||
/// <summary>
|
||||
/// Invokes the authorization filters for the controller action.
|
||||
/// </summary>
|
||||
/// <returns>Whether the user is authenticated or not.</returns>
|
||||
internal static async Task<bool> InvokeAuthorizationFiltersForRequest(this ControllerContext controllerContext, ActionContext actionContext)
|
||||
{
|
||||
/// <summary>
|
||||
/// Invokes the authorization filters for the controller action.
|
||||
/// </summary>
|
||||
/// <returns>Whether the user is authenticated or not.</returns>
|
||||
internal static async Task<bool> InvokeAuthorizationFiltersForRequest(this ControllerContext controllerContext, ActionContext actionContext)
|
||||
ControllerActionDescriptor actionDescriptor = controllerContext.ActionDescriptor;
|
||||
|
||||
var metadataCollection =
|
||||
new EndpointMetadataCollection(actionDescriptor.EndpointMetadata.Union(new[] { actionDescriptor }));
|
||||
|
||||
IReadOnlyList<IAuthorizeData> authorizeData = metadataCollection.GetOrderedMetadata<IAuthorizeData>();
|
||||
IAuthorizationPolicyProvider policyProvider = controllerContext.HttpContext.RequestServices
|
||||
.GetRequiredService<IAuthorizationPolicyProvider>();
|
||||
AuthorizationPolicy? policy = await AuthorizationPolicy.CombineAsync(policyProvider, authorizeData);
|
||||
if (policy is not null)
|
||||
{
|
||||
IPolicyEvaluator policyEvaluator =
|
||||
controllerContext.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();
|
||||
AuthenticateResult authenticateResult =
|
||||
await policyEvaluator.AuthenticateAsync(policy, controllerContext.HttpContext);
|
||||
|
||||
var actionDescriptor = controllerContext.ActionDescriptor;
|
||||
|
||||
var metadataCollection = new EndpointMetadataCollection(actionDescriptor.EndpointMetadata.Union(new []{actionDescriptor}));
|
||||
|
||||
var authorizeData = metadataCollection.GetOrderedMetadata<IAuthorizeData>();
|
||||
var policyProvider = controllerContext.HttpContext.RequestServices.GetRequiredService<IAuthorizationPolicyProvider>();
|
||||
var policy = await AuthorizationPolicy.CombineAsync(policyProvider, authorizeData);
|
||||
if (policy is not null)
|
||||
if (!authenticateResult.Succeeded)
|
||||
{
|
||||
var policyEvaluator = controllerContext.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();
|
||||
var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, controllerContext.HttpContext);
|
||||
|
||||
if (!authenticateResult.Succeeded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO this is super hacky, but we rely on the FeatureAuthorizeHandler can still handle endpoints
|
||||
// (The way before .NET 5). The .NET 5 way would need to use han http context, for the "inner" request
|
||||
// with the nested controller
|
||||
var resource = new Endpoint(null,metadataCollection, null);
|
||||
var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult, controllerContext.HttpContext, resource);
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var filters = actionDescriptor.FilterDescriptors;
|
||||
var filterGrouping = new FilterGrouping(filters, controllerContext.HttpContext.RequestServices);
|
||||
|
||||
// because the continuation gets built from the inside out we need to reverse the filter list
|
||||
// so that least specific filters (Global) get run first and the most specific filters (Action) get run last.
|
||||
var authorizationFilters = filterGrouping.AuthorizationFilters.Reverse().ToList();
|
||||
var asyncAuthorizationFilters = filterGrouping.AsyncAuthorizationFilters.Reverse().ToList();
|
||||
|
||||
if (authorizationFilters.Count == 0 && asyncAuthorizationFilters.Count == 0)
|
||||
return true;
|
||||
|
||||
// if the authorization filter returns a result, it means it failed to authorize
|
||||
var authorizationFilterContext = new AuthorizationFilterContext(actionContext, filters.Select(x=>x.Filter).ToArray());
|
||||
return await ExecuteAuthorizationFiltersAsync(authorizationFilterContext, authorizationFilters, asyncAuthorizationFilters);
|
||||
// TODO this is super hacky, but we rely on the FeatureAuthorizeHandler can still handle endpoints
|
||||
// (The way before .NET 5). The .NET 5 way would need to use han http context, for the "inner" request
|
||||
// with the nested controller
|
||||
var resource = new Endpoint(null, metadataCollection, null);
|
||||
PolicyAuthorizationResult authorizeResult =
|
||||
await policyEvaluator.AuthorizeAsync(policy, authenticateResult, controllerContext.HttpContext, resource);
|
||||
if (!authorizeResult.Succeeded)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a chain of filters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Recursively calls in to itself as its continuation for the next filter in the chain.
|
||||
/// </remarks>
|
||||
private static async Task<bool> ExecuteAuthorizationFiltersAsync(
|
||||
AuthorizationFilterContext authorizationFilterContext,
|
||||
IList<IAuthorizationFilter> authorizationFilters,
|
||||
IList<IAsyncAuthorizationFilter> asyncAuthorizationFilters)
|
||||
IList<FilterDescriptor> filters = actionDescriptor.FilterDescriptors;
|
||||
var filterGrouping = new FilterGrouping(filters, controllerContext.HttpContext.RequestServices);
|
||||
|
||||
// because the continuation gets built from the inside out we need to reverse the filter list
|
||||
// so that least specific filters (Global) get run first and the most specific filters (Action) get run last.
|
||||
var authorizationFilters = filterGrouping.AuthorizationFilters.Reverse().ToList();
|
||||
var asyncAuthorizationFilters = filterGrouping.AsyncAuthorizationFilters.Reverse().ToList();
|
||||
|
||||
if (authorizationFilters.Count == 0 && asyncAuthorizationFilters.Count == 0)
|
||||
{
|
||||
|
||||
foreach (var authorizationFilter in authorizationFilters)
|
||||
{
|
||||
authorizationFilter.OnAuthorization(authorizationFilterContext);
|
||||
if (!(authorizationFilterContext.Result is null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
foreach (var asyncAuthorizationFilter in asyncAuthorizationFilters)
|
||||
{
|
||||
await asyncAuthorizationFilter.OnAuthorizationAsync(authorizationFilterContext);
|
||||
if (!(authorizationFilterContext.Result is null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quickly split filters into different types
|
||||
/// </summary>
|
||||
private class FilterGrouping
|
||||
// if the authorization filter returns a result, it means it failed to authorize
|
||||
var authorizationFilterContext =
|
||||
new AuthorizationFilterContext(actionContext, filters.Select(x => x.Filter).ToArray());
|
||||
return await ExecuteAuthorizationFiltersAsync(authorizationFilterContext, authorizationFilters, asyncAuthorizationFilters);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes a chain of filters.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Recursively calls in to itself as its continuation for the next filter in the chain.
|
||||
/// </remarks>
|
||||
private static async Task<bool> ExecuteAuthorizationFiltersAsync(
|
||||
AuthorizationFilterContext authorizationFilterContext,
|
||||
IList<IAuthorizationFilter> authorizationFilters,
|
||||
IList<IAsyncAuthorizationFilter> asyncAuthorizationFilters)
|
||||
{
|
||||
foreach (IAuthorizationFilter authorizationFilter in authorizationFilters)
|
||||
{
|
||||
private readonly List<IActionFilter> _actionFilters = new List<IActionFilter>();
|
||||
private readonly List<IAsyncActionFilter> _asyncActionFilters = new List<IAsyncActionFilter>();
|
||||
private readonly List<IAuthorizationFilter> _authorizationFilters = new List<IAuthorizationFilter>();
|
||||
private readonly List<IAsyncAuthorizationFilter> _asyncAuthorizationFilters = new List<IAsyncAuthorizationFilter>();
|
||||
private readonly List<IExceptionFilter> _exceptionFilters = new List<IExceptionFilter>();
|
||||
private readonly List<IAsyncExceptionFilter> _asyncExceptionFilters = new List<IAsyncExceptionFilter>();
|
||||
|
||||
public FilterGrouping(IEnumerable<FilterDescriptor> filters, IServiceProvider serviceProvider)
|
||||
authorizationFilter.OnAuthorization(authorizationFilterContext);
|
||||
if (!(authorizationFilterContext.Result is null))
|
||||
{
|
||||
if (filters == null) throw new ArgumentNullException("filters");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (FilterDescriptor f in filters)
|
||||
{
|
||||
var filter = f.Filter;
|
||||
Categorize(filter, _actionFilters, serviceProvider);
|
||||
Categorize(filter, _authorizationFilters, serviceProvider);
|
||||
Categorize(filter, _exceptionFilters, serviceProvider);
|
||||
Categorize(filter, _asyncActionFilters, serviceProvider);
|
||||
Categorize(filter, _asyncAuthorizationFilters, serviceProvider);
|
||||
}
|
||||
foreach (IAsyncAuthorizationFilter asyncAuthorizationFilter in asyncAuthorizationFilters)
|
||||
{
|
||||
await asyncAuthorizationFilter.OnAuthorizationAsync(authorizationFilterContext);
|
||||
if (!(authorizationFilterContext.Result is null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Quickly split filters into different types
|
||||
/// </summary>
|
||||
private class FilterGrouping
|
||||
{
|
||||
private readonly List<IActionFilter> _actionFilters = new();
|
||||
private readonly List<IAsyncActionFilter> _asyncActionFilters = new();
|
||||
private readonly List<IAsyncAuthorizationFilter> _asyncAuthorizationFilters = new();
|
||||
private readonly List<IAsyncExceptionFilter> _asyncExceptionFilters = new();
|
||||
private readonly List<IAuthorizationFilter> _authorizationFilters = new();
|
||||
private readonly List<IExceptionFilter> _exceptionFilters = new();
|
||||
|
||||
public FilterGrouping(IEnumerable<FilterDescriptor> filters, IServiceProvider serviceProvider)
|
||||
{
|
||||
if (filters == null)
|
||||
{
|
||||
throw new ArgumentNullException("filters");
|
||||
}
|
||||
|
||||
public IEnumerable<IActionFilter> ActionFilters => _actionFilters;
|
||||
public IEnumerable<IAsyncActionFilter> AsyncActionFilters => _asyncActionFilters;
|
||||
public IEnumerable<IAuthorizationFilter> AuthorizationFilters => _authorizationFilters;
|
||||
|
||||
public IEnumerable<IAsyncAuthorizationFilter> AsyncAuthorizationFilters => _asyncAuthorizationFilters;
|
||||
|
||||
public IEnumerable<IExceptionFilter> ExceptionFilters => _exceptionFilters;
|
||||
|
||||
public IEnumerable<IAsyncExceptionFilter> AsyncExceptionFilters => _asyncExceptionFilters;
|
||||
|
||||
private static void Categorize<T>(IFilterMetadata filter, List<T> list, IServiceProvider serviceProvider) where T : class
|
||||
foreach (FilterDescriptor f in filters)
|
||||
{
|
||||
if(filter is TypeFilterAttribute typeFilterAttribute)
|
||||
{
|
||||
filter = typeFilterAttribute.CreateInstance(serviceProvider);
|
||||
}
|
||||
IFilterMetadata filter = f.Filter;
|
||||
Categorize(filter, _actionFilters, serviceProvider);
|
||||
Categorize(filter, _authorizationFilters, serviceProvider);
|
||||
Categorize(filter, _exceptionFilters, serviceProvider);
|
||||
Categorize(filter, _asyncActionFilters, serviceProvider);
|
||||
Categorize(filter, _asyncAuthorizationFilters, serviceProvider);
|
||||
}
|
||||
}
|
||||
|
||||
T? match = filter as T;
|
||||
if (match != null)
|
||||
{
|
||||
list.Add(match);
|
||||
}
|
||||
public IEnumerable<IActionFilter> ActionFilters => _actionFilters;
|
||||
public IEnumerable<IAsyncActionFilter> AsyncActionFilters => _asyncActionFilters;
|
||||
public IEnumerable<IAuthorizationFilter> AuthorizationFilters => _authorizationFilters;
|
||||
|
||||
public IEnumerable<IAsyncAuthorizationFilter> AsyncAuthorizationFilters => _asyncAuthorizationFilters;
|
||||
|
||||
public IEnumerable<IExceptionFilter> ExceptionFilters => _exceptionFilters;
|
||||
|
||||
public IEnumerable<IAsyncExceptionFilter> AsyncExceptionFilters => _asyncExceptionFilters;
|
||||
|
||||
private static void Categorize<T>(IFilterMetadata filter, List<T> list, IServiceProvider serviceProvider)
|
||||
where T : class
|
||||
{
|
||||
if (filter is TypeFilterAttribute typeFilterAttribute)
|
||||
{
|
||||
filter = typeFilterAttribute.CreateInstance(serviceProvider);
|
||||
}
|
||||
|
||||
if (filter is T match)
|
||||
{
|
||||
list.Add(match);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Html;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Newtonsoft.Json;
|
||||
@@ -11,131 +8,138 @@ using Umbraco.Cms.Infrastructure.WebAssets;
|
||||
using Umbraco.Cms.Web.BackOffice.Controllers;
|
||||
using Umbraco.Cms.Web.BackOffice.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
public static class HtmlHelperBackOfficeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Outputs a script tag containing the bare minimum (non secure) server vars for use with the angular app
|
||||
/// </summary>
|
||||
/// <param name="html"></param>
|
||||
/// <param name="linkGenerator"></param>
|
||||
/// <param name="features"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="umbracoVersion"></param>
|
||||
/// <param name="contentSettings"></param>
|
||||
/// <param name="treeCollection"></param>
|
||||
/// <param name="httpContextAccessor"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="settings"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="runtimeMinifier"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// These are the bare minimal server variables that are required for the application to start without being authenticated,
|
||||
/// we will load the rest of the server vars after the user is authenticated.
|
||||
/// </remarks>
|
||||
public static async Task<IHtmlContent> BareMinimumServerVariablesScriptAsync(this IHtmlHelper html, BackOfficeServerVariables backOfficeServerVariables)
|
||||
{
|
||||
var minVars = await backOfficeServerVariables.BareMinimumServerVariablesAsync();
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
var str = @"<script type=""text/javascript"">
|
||||
public static class HtmlHelperBackOfficeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Outputs a script tag containing the bare minimum (non secure) server vars for use with the angular app
|
||||
/// </summary>
|
||||
/// <param name="html"></param>
|
||||
/// <param name="linkGenerator"></param>
|
||||
/// <param name="features"></param>
|
||||
/// <param name="globalSettings"></param>
|
||||
/// <param name="umbracoVersion"></param>
|
||||
/// <param name="contentSettings"></param>
|
||||
/// <param name="treeCollection"></param>
|
||||
/// <param name="httpContextAccessor"></param>
|
||||
/// <param name="hostingEnvironment"></param>
|
||||
/// <param name="settings"></param>
|
||||
/// <param name="securitySettings"></param>
|
||||
/// <param name="runtimeMinifier"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// These are the bare minimal server variables that are required for the application to start without being
|
||||
/// authenticated,
|
||||
/// we will load the rest of the server vars after the user is authenticated.
|
||||
/// </remarks>
|
||||
public static async Task<IHtmlContent> BareMinimumServerVariablesScriptAsync(this IHtmlHelper html,
|
||||
BackOfficeServerVariables backOfficeServerVariables)
|
||||
{
|
||||
Dictionary<string, object> minVars = await backOfficeServerVariables.BareMinimumServerVariablesAsync();
|
||||
|
||||
var str = @"<script type=""text/javascript"">
|
||||
var Umbraco = {};
|
||||
Umbraco.Sys = {};
|
||||
Umbraco.Sys.ServerVariables = " + JsonConvert.SerializeObject(minVars) + @";
|
||||
</script>";
|
||||
|
||||
return html.Raw(str);
|
||||
}
|
||||
return html.Raw(str);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to render the script that will pass in the angular "externalLoginInfo" service/value on page load
|
||||
/// </summary>
|
||||
/// <param name="html"></param>
|
||||
/// <param name="externalLogins"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<IHtmlContent> AngularValueExternalLoginInfoScriptAsync(this IHtmlHelper html,
|
||||
IBackOfficeExternalLoginProviders externalLogins,
|
||||
BackOfficeExternalLoginProviderErrors externalLoginErrors)
|
||||
{
|
||||
var providers = await externalLogins.GetBackOfficeProvidersAsync();
|
||||
/// <summary>
|
||||
/// Used to render the script that will pass in the angular "externalLoginInfo" service/value on page load
|
||||
/// </summary>
|
||||
/// <param name="html"></param>
|
||||
/// <param name="externalLogins"></param>
|
||||
/// <returns></returns>
|
||||
public static async Task<IHtmlContent> AngularValueExternalLoginInfoScriptAsync(this IHtmlHelper html,
|
||||
IBackOfficeExternalLoginProviders externalLogins,
|
||||
BackOfficeExternalLoginProviderErrors externalLoginErrors)
|
||||
{
|
||||
IEnumerable<BackOfficeExternaLoginProviderScheme>
|
||||
providers = await externalLogins.GetBackOfficeProvidersAsync();
|
||||
|
||||
var loginProviders = providers
|
||||
.Select(p => new
|
||||
{
|
||||
authType = p.ExternalLoginProvider.AuthenticationType,
|
||||
caption = p.AuthenticationScheme.DisplayName,
|
||||
properties = p.ExternalLoginProvider.Options
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(@"var errors = [];");
|
||||
|
||||
if (externalLoginErrors != null)
|
||||
var loginProviders = providers
|
||||
.Select(p => new
|
||||
{
|
||||
if (externalLoginErrors.Errors is not null)
|
||||
authType = p.ExternalLoginProvider.AuthenticationType,
|
||||
caption = p.AuthenticationScheme.DisplayName,
|
||||
properties = p.ExternalLoginProvider.Options
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(@"var errors = [];");
|
||||
|
||||
if (externalLoginErrors != null)
|
||||
{
|
||||
if (externalLoginErrors.Errors is not null)
|
||||
{
|
||||
foreach (var error in externalLoginErrors.Errors)
|
||||
{
|
||||
foreach (var error in externalLoginErrors.Errors)
|
||||
{
|
||||
sb.AppendFormat(@"errors.push(""{0}"");", error.ToSingleLine()).AppendLine();
|
||||
}
|
||||
sb.AppendFormat(@"errors.push(""{0}"");", error.ToSingleLine()).AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine(@"app.value(""externalLoginInfo"", {");
|
||||
if (externalLoginErrors?.AuthenticationType != null)
|
||||
sb.AppendLine($@"errorProvider: '{externalLoginErrors.AuthenticationType}',");
|
||||
sb.AppendLine(@"errors: errors,");
|
||||
sb.Append(@"providers: ");
|
||||
sb.AppendLine(JsonConvert.SerializeObject(loginProviders));
|
||||
sb.AppendLine(@"});");
|
||||
|
||||
return html.Raw(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to render the script that will pass in the angular "resetPasswordCodeInfo" service/value on page load
|
||||
/// </summary>
|
||||
/// <param name="html"></param>
|
||||
/// <param name="val"></param>
|
||||
/// <returns></returns>
|
||||
public static IHtmlContent AngularValueResetPasswordCodeInfoScript(this IHtmlHelper html, object val)
|
||||
sb.AppendLine(@"app.value(""externalLoginInfo"", {");
|
||||
if (externalLoginErrors?.AuthenticationType != null)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(@"var errors = [];");
|
||||
sb.AppendLine($@"errorProvider: '{externalLoginErrors.AuthenticationType}',");
|
||||
}
|
||||
|
||||
if (val is IEnumerable<string> errors)
|
||||
sb.AppendLine(@"errors: errors,");
|
||||
sb.Append(@"providers: ");
|
||||
sb.AppendLine(JsonConvert.SerializeObject(loginProviders));
|
||||
sb.AppendLine(@"});");
|
||||
|
||||
return html.Raw(sb.ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to render the script that will pass in the angular "resetPasswordCodeInfo" service/value on page load
|
||||
/// </summary>
|
||||
/// <param name="html"></param>
|
||||
/// <param name="val"></param>
|
||||
/// <returns></returns>
|
||||
public static IHtmlContent AngularValueResetPasswordCodeInfoScript(this IHtmlHelper html, object val)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(@"var errors = [];");
|
||||
|
||||
if (val is IEnumerable<string> errors)
|
||||
{
|
||||
foreach (var error in errors)
|
||||
{
|
||||
foreach (var error in errors)
|
||||
{
|
||||
sb.AppendFormat(@"errors.push(""{0}"");", error).AppendLine();
|
||||
}
|
||||
sb.AppendFormat(@"errors.push(""{0}"");", error).AppendLine();
|
||||
}
|
||||
|
||||
sb.AppendLine(@"app.value(""resetPasswordCodeInfo"", {");
|
||||
sb.AppendLine(@"errors: errors,");
|
||||
sb.Append(@"resetCodeModel: ");
|
||||
sb.AppendLine(val?.ToString() ?? "null");
|
||||
sb.AppendLine(@"});");
|
||||
|
||||
return html.Raw(sb.ToString());
|
||||
}
|
||||
|
||||
public static async Task<IHtmlContent> AngularValueTinyMceAssetsAsync(this IHtmlHelper html, IRuntimeMinifier runtimeMinifier)
|
||||
{
|
||||
var files = await runtimeMinifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoTinyMceJsBundleName);
|
||||
sb.AppendLine(@"app.value(""resetPasswordCodeInfo"", {");
|
||||
sb.AppendLine(@"errors: errors,");
|
||||
sb.Append(@"resetCodeModel: ");
|
||||
sb.AppendLine(val?.ToString() ?? "null");
|
||||
sb.AppendLine(@"});");
|
||||
|
||||
var sb = new StringBuilder();
|
||||
return html.Raw(sb.ToString());
|
||||
}
|
||||
|
||||
sb.AppendLine(@"app.value(""tinyMceAssets"",");
|
||||
sb.AppendLine(JsonConvert.SerializeObject(files));
|
||||
sb.AppendLine(@");");
|
||||
public static async Task<IHtmlContent> AngularValueTinyMceAssetsAsync(this IHtmlHelper html,
|
||||
IRuntimeMinifier runtimeMinifier)
|
||||
{
|
||||
IEnumerable<string> files =
|
||||
await runtimeMinifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoTinyMceJsBundleName);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine(@"app.value(""tinyMceAssets"",");
|
||||
sb.AppendLine(JsonConvert.SerializeObject(files));
|
||||
sb.AppendLine(@");");
|
||||
|
||||
|
||||
return html.Raw(sb.ToString());
|
||||
}
|
||||
return html.Raw(sb.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
public static class HttpContextExtensions
|
||||
{
|
||||
public static class HttpContextExtensions
|
||||
{
|
||||
public static void SetExternalLoginProviderErrors(this HttpContext httpContext, BackOfficeExternalLoginProviderErrors errors)
|
||||
=> httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] = errors;
|
||||
public static void SetExternalLoginProviderErrors(this HttpContext httpContext, BackOfficeExternalLoginProviderErrors errors)
|
||||
=> httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] = errors;
|
||||
|
||||
public static BackOfficeExternalLoginProviderErrors? GetExternalLoginProviderErrors(this HttpContext httpContext)
|
||||
=> httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] as BackOfficeExternalLoginProviderErrors;
|
||||
|
||||
}
|
||||
public static BackOfficeExternalLoginProviderErrors? GetExternalLoginProviderErrors(this HttpContext httpContext)
|
||||
=> httpContext.Items[nameof(BackOfficeExternalLoginProviderErrors)] as BackOfficeExternalLoginProviderErrors;
|
||||
}
|
||||
|
||||
@@ -2,41 +2,40 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Security;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IdentityBuilder" />
|
||||
/// </summary>
|
||||
public static class IdentityBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Extension methods for <see cref="IdentityBuilder"/>
|
||||
/// Adds a <see cref="UserManager{TUser}" /> implementation for <seealso cref="BackOfficeIdentityUser" />
|
||||
/// </summary>
|
||||
public static class IdentityBuilderExtensions
|
||||
/// <typeparam name="TInterface">The usermanager interface</typeparam>
|
||||
/// <typeparam name="TUserManager">The usermanager type</typeparam>
|
||||
/// <param name="identityBuilder">The <see cref="IdentityBuilder" /></param>
|
||||
/// <returns>The current <see cref="IdentityBuilder" /> instance.</returns>
|
||||
public static IdentityBuilder AddUserManager<TInterface, TUserManager>(this IdentityBuilder identityBuilder)
|
||||
where TUserManager : UserManager<BackOfficeIdentityUser>, TInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a <see cref="UserManager{TUser}"/> implementation for <seealso cref="BackOfficeIdentityUser"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">The usermanager interface</typeparam>
|
||||
/// <typeparam name="TUserManager">The usermanager type</typeparam>
|
||||
/// <param name="identityBuilder">The <see cref="IdentityBuilder"/></param>
|
||||
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
|
||||
public static IdentityBuilder AddUserManager<TInterface, TUserManager>(this IdentityBuilder identityBuilder)
|
||||
where TUserManager : UserManager<BackOfficeIdentityUser>, TInterface
|
||||
{
|
||||
identityBuilder.AddUserManager<TUserManager>();
|
||||
identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TUserManager));
|
||||
return identityBuilder;
|
||||
}
|
||||
identityBuilder.AddUserManager<TUserManager>();
|
||||
identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TUserManager));
|
||||
return identityBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="SignInManager{TUser}"/> implementation for <seealso cref="BackOfficeIdentityUser"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">The sign in manager interface</typeparam>
|
||||
/// <typeparam name="TSignInManager">The sign in manager type</typeparam>
|
||||
/// <param name="identityBuilder">The <see cref="IdentityBuilder"/></param>
|
||||
/// <returns>The current <see cref="IdentityBuilder"/> instance.</returns>
|
||||
public static IdentityBuilder AddSignInManager<TInterface, TSignInManager>(this IdentityBuilder identityBuilder)
|
||||
where TSignInManager : SignInManager<BackOfficeIdentityUser>, TInterface
|
||||
{
|
||||
identityBuilder.AddSignInManager<TSignInManager>();
|
||||
identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TSignInManager));
|
||||
return identityBuilder;
|
||||
}
|
||||
/// <summary>
|
||||
/// Adds a <see cref="SignInManager{TUser}" /> implementation for <seealso cref="BackOfficeIdentityUser" />
|
||||
/// </summary>
|
||||
/// <typeparam name="TInterface">The sign in manager interface</typeparam>
|
||||
/// <typeparam name="TSignInManager">The sign in manager type</typeparam>
|
||||
/// <param name="identityBuilder">The <see cref="IdentityBuilder" /></param>
|
||||
/// <returns>The current <see cref="IdentityBuilder" /> instance.</returns>
|
||||
public static IdentityBuilder AddSignInManager<TInterface, TSignInManager>(this IdentityBuilder identityBuilder)
|
||||
where TSignInManager : SignInManager<BackOfficeIdentityUser>, TInterface
|
||||
{
|
||||
identityBuilder.AddSignInManager<TSignInManager>();
|
||||
identityBuilder.Services.AddScoped(typeof(TInterface), typeof(TSignInManager));
|
||||
return identityBuilder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,23 @@
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Umbraco.Cms.Core;
|
||||
using Umbraco.Cms.Web.BackOffice.Install;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
public static class BackofficeLinkGeneratorExtensions
|
||||
{
|
||||
public static class BackofficeLinkGeneratorExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the URL for the installer
|
||||
/// </summary>
|
||||
public static string? GetInstallerUrl(this LinkGenerator linkGenerator)
|
||||
=> linkGenerator.GetPathByAction(nameof(InstallController.Index), ControllerExtensions.GetControllerName<InstallController>(), new { area = Cms.Core.Constants.Web.Mvc.InstallArea });
|
||||
/// <summary>
|
||||
/// Returns the URL for the installer
|
||||
/// </summary>
|
||||
public static string? GetInstallerUrl(this LinkGenerator linkGenerator)
|
||||
=> linkGenerator.GetPathByAction(nameof(InstallController.Index), ControllerExtensions.GetControllerName<InstallController>(), new { area = Constants.Web.Mvc.InstallArea });
|
||||
|
||||
/// <summary>
|
||||
/// Returns the URL for the installer api
|
||||
/// </summary>
|
||||
public static string? GetInstallerApiUrl(this LinkGenerator linkGenerator)
|
||||
=> linkGenerator.GetPathByAction(
|
||||
nameof(InstallApiController.GetSetup),
|
||||
ControllerExtensions.GetControllerName<InstallApiController>(),
|
||||
new { area = Cms.Core.Constants.Web.Mvc.InstallArea })?.TrimEnd(nameof(InstallApiController.GetSetup));
|
||||
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the URL for the installer api
|
||||
/// </summary>
|
||||
public static string? GetInstallerApiUrl(this LinkGenerator linkGenerator)
|
||||
=> linkGenerator.GetPathByAction(
|
||||
nameof(InstallApiController.GetSetup),
|
||||
ControllerExtensions.GetControllerName<InstallApiController>(),
|
||||
new { area = Constants.Web.Mvc.InstallArea })?.TrimEnd(nameof(InstallApiController.GetSetup));
|
||||
}
|
||||
|
||||
@@ -1,168 +1,169 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Umbraco.Cms.Web.BackOffice.PropertyEditors.Validation;
|
||||
using Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
public static class ModelStateExtensions
|
||||
{
|
||||
public static class ModelStateExtensions
|
||||
/// <summary>
|
||||
/// Adds the <see cref="ValidationResult" /> to the model state with the appropriate keys for property errors
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="propertyAlias"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="segment"></param>
|
||||
internal static void AddPropertyValidationError(this ModelStateDictionary modelState,
|
||||
ValidationResult result,
|
||||
string propertyAlias,
|
||||
string culture = "",
|
||||
string segment = "") =>
|
||||
modelState.AddValidationError(
|
||||
result,
|
||||
"_Properties",
|
||||
propertyAlias,
|
||||
//if the culture is null, we'll add the term 'invariant' as part of the key
|
||||
culture.IsNullOrWhiteSpace() ? "invariant" : culture,
|
||||
// if the segment is null, we'll add the term 'null' as part of the key
|
||||
segment.IsNullOrWhiteSpace() ? "null" : segment);
|
||||
|
||||
/// <summary>
|
||||
/// Adds an <see cref="ContentPropertyValidationResult" /> error to model state for a property so we can use it on the
|
||||
/// client side.
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="propertyAlias"></param>
|
||||
/// <param name="culture">The culture for the property, if the property is invariant than this is empty</param>
|
||||
internal static void AddPropertyError(this ModelStateDictionary modelState,
|
||||
ValidationResult result, string propertyAlias, string culture = "", string segment = "") =>
|
||||
modelState.AddPropertyValidationError(new ContentPropertyValidationResult(result, culture, segment),
|
||||
propertyAlias, culture, segment);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="segment"></param>
|
||||
/// <param name="errMsg"></param>
|
||||
internal static void AddVariantValidationError(this ModelStateDictionary modelState,
|
||||
string? culture, string? segment, string errMsg)
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds the <see cref="ValidationResult"/> to the model state with the appropriate keys for property errors
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="propertyAlias"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="segment"></param>
|
||||
internal static void AddPropertyValidationError(this ModelStateDictionary modelState,
|
||||
ValidationResult result,
|
||||
string propertyAlias,
|
||||
string culture = "",
|
||||
string segment = "") =>
|
||||
modelState.AddValidationError(
|
||||
result,
|
||||
"_Properties",
|
||||
propertyAlias,
|
||||
//if the culture is null, we'll add the term 'invariant' as part of the key
|
||||
culture.IsNullOrWhiteSpace() ? "invariant" : culture,
|
||||
// if the segment is null, we'll add the term 'null' as part of the key
|
||||
segment.IsNullOrWhiteSpace() ? "null" : segment);
|
||||
|
||||
/// <summary>
|
||||
/// Adds an <see cref="ContentPropertyValidationResult"/> error to model state for a property so we can use it on the client side.
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="propertyAlias"></param>
|
||||
/// <param name="culture">The culture for the property, if the property is invariant than this is empty</param>
|
||||
internal static void AddPropertyError(this ModelStateDictionary modelState,
|
||||
ValidationResult result, string propertyAlias, string culture = "", string segment = "") =>
|
||||
modelState.AddPropertyValidationError(new ContentPropertyValidationResult(result, culture, segment), propertyAlias, culture, segment);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a generic culture error for use in displaying the culture validation error in the save/publish/etc... dialogs
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="culture"></param>
|
||||
/// <param name="segment"></param>
|
||||
/// <param name="errMsg"></param>
|
||||
internal static void AddVariantValidationError(this ModelStateDictionary modelState,
|
||||
string? culture, string? segment, string errMsg)
|
||||
var key = "_content_variant_" + (culture.IsNullOrWhiteSpace() ? "invariant" : culture) + "_" +
|
||||
(segment.IsNullOrWhiteSpace() ? "null" : segment) + "_";
|
||||
if (modelState.ContainsKey(key))
|
||||
{
|
||||
var key = "_content_variant_" + (culture.IsNullOrWhiteSpace() ? "invariant" : culture) + "_" + (segment.IsNullOrWhiteSpace() ? "null" : segment) + "_";
|
||||
if (modelState.ContainsKey(key))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
modelState.AddModelError(key, errMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of cultures that have property validation errors
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="localizationService"></param>
|
||||
/// <param name="cultureForInvariantErrors">The culture to affiliate invariant errors with</param>
|
||||
/// <returns>
|
||||
/// A list of cultures that have property validation errors. The default culture will be returned for any invariant property errors.
|
||||
/// </returns>
|
||||
internal static IReadOnlyList<(string? culture, string? segment)>? GetVariantsWithPropertyErrors(this ModelStateDictionary modelState,
|
||||
string? cultureForInvariantErrors)
|
||||
{
|
||||
//Add any variant specific errors here
|
||||
var variantErrors = modelState.Keys
|
||||
.Where(key => key.StartsWith("_Properties.")) //only choose _Properties errors
|
||||
.Select(x => x.Split('.')) //split into parts
|
||||
.Where(x => x.Length >= 4 && !x[2].IsNullOrWhiteSpace() && !x[3].IsNullOrWhiteSpace())
|
||||
.Select(x => (culture: x[2], segment: x[3]))
|
||||
//if the culture is marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language
|
||||
//so errors for those must show up under the default lang.
|
||||
//if the segment is marked "null" then return an actual null
|
||||
.Select(x =>
|
||||
{
|
||||
var culture = x.culture == "invariant" ? cultureForInvariantErrors : x.culture;
|
||||
var segment = x.segment == "null" ? null : x.segment;
|
||||
return (culture, segment);
|
||||
})
|
||||
.Distinct()
|
||||
.ToList();
|
||||
modelState.AddModelError(key, errMsg);
|
||||
}
|
||||
|
||||
return variantErrors;
|
||||
/// <summary>
|
||||
/// Returns a list of cultures that have property validation errors
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="localizationService"></param>
|
||||
/// <param name="cultureForInvariantErrors">The culture to affiliate invariant errors with</param>
|
||||
/// <returns>
|
||||
/// A list of cultures that have property validation errors. The default culture will be returned for any invariant
|
||||
/// property errors.
|
||||
/// </returns>
|
||||
internal static IReadOnlyList<(string? culture, string? segment)>? GetVariantsWithPropertyErrors(
|
||||
this ModelStateDictionary modelState,
|
||||
string? cultureForInvariantErrors)
|
||||
{
|
||||
//Add any variant specific errors here
|
||||
var variantErrors = modelState.Keys
|
||||
.Where(key => key.StartsWith("_Properties.")) //only choose _Properties errors
|
||||
.Select(x => x.Split('.')) //split into parts
|
||||
.Where(x => x.Length >= 4 && !x[2].IsNullOrWhiteSpace() && !x[3].IsNullOrWhiteSpace())
|
||||
.Select(x => (culture: x[2], segment: x[3]))
|
||||
//if the culture is marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language
|
||||
//so errors for those must show up under the default lang.
|
||||
//if the segment is marked "null" then return an actual null
|
||||
.Select(x =>
|
||||
{
|
||||
var culture = x.culture == "invariant" ? cultureForInvariantErrors : x.culture;
|
||||
var segment = x.segment == "null" ? null : x.segment;
|
||||
return (culture, segment);
|
||||
})
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
return variantErrors;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of cultures that have any validation errors
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="localizationService"></param>
|
||||
/// <param name="cultureForInvariantErrors">The culture to affiliate invariant errors with</param>
|
||||
/// <returns>
|
||||
/// A list of cultures that have validation errors. The default culture will be returned for any invariant errors.
|
||||
/// </returns>
|
||||
internal static IReadOnlyList<(string? culture, string? segment)>? GetVariantsWithErrors(
|
||||
this ModelStateDictionary modelState, string? cultureForInvariantErrors)
|
||||
{
|
||||
IReadOnlyList<(string? culture, string? segment)>? propertyVariantErrors =
|
||||
modelState.GetVariantsWithPropertyErrors(cultureForInvariantErrors);
|
||||
|
||||
//now check the other special variant errors that are
|
||||
IEnumerable<(string? culture, string? segment)>? genericVariantErrors = modelState.Keys
|
||||
.Where(x => x.StartsWith("_content_variant_") && x.EndsWith("_"))
|
||||
.Select(x => x.TrimStart("_content_variant_").TrimEnd("_"))
|
||||
.Select(x =>
|
||||
{
|
||||
// Format "<culture>_<segment>"
|
||||
var cs = x.Split(new[] { '_' });
|
||||
return (culture: cs[0], segment: cs[1]);
|
||||
})
|
||||
.Where(x => !x.culture.IsNullOrWhiteSpace())
|
||||
//if it's marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language
|
||||
//so errors for those must show up under the default lang.
|
||||
//if the segment is marked "null" then return an actual null
|
||||
.Select(x =>
|
||||
{
|
||||
var culture = x.culture == "invariant" ? cultureForInvariantErrors : x.culture;
|
||||
var segment = x.segment == "null" ? null : x.segment;
|
||||
return (culture, segment);
|
||||
})
|
||||
.Distinct();
|
||||
|
||||
return propertyVariantErrors?.Union(genericVariantErrors).Distinct().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the error to model state correctly for a property so we can use it on the client side.
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="parts">
|
||||
/// Each model state validation error has a name and in most cases this name is made up of parts which are delimited by
|
||||
/// a '.'
|
||||
/// </param>
|
||||
internal static void AddValidationError(this ModelStateDictionary modelState,
|
||||
ValidationResult result, params string[] parts)
|
||||
{
|
||||
// if there are assigned member names, we combine the member name with the owner name
|
||||
// so that we can try to match it up to a real field. otherwise, we assume that the
|
||||
// validation message is for the overall owner.
|
||||
// Owner = the component being validated, like a content property but could be just an HTML field on another editor
|
||||
|
||||
var withNames = false;
|
||||
var delimitedParts = string.Join(".", parts);
|
||||
foreach (var memberName in result.MemberNames)
|
||||
{
|
||||
modelState.TryAddModelError($"{delimitedParts}.{memberName}", result.ErrorMessage ?? string.Empty);
|
||||
withNames = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of cultures that have any validation errors
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="localizationService"></param>
|
||||
/// <param name="cultureForInvariantErrors">The culture to affiliate invariant errors with</param>
|
||||
/// <returns>
|
||||
/// A list of cultures that have validation errors. The default culture will be returned for any invariant errors.
|
||||
/// </returns>
|
||||
internal static IReadOnlyList<(string? culture, string? segment)>? GetVariantsWithErrors(this ModelStateDictionary modelState, string? cultureForInvariantErrors)
|
||||
if (!withNames)
|
||||
{
|
||||
IReadOnlyList<(string? culture, string? segment)>? propertyVariantErrors = modelState.GetVariantsWithPropertyErrors(cultureForInvariantErrors);
|
||||
|
||||
//now check the other special variant errors that are
|
||||
IEnumerable<(string? culture, string? segment)>? genericVariantErrors = modelState.Keys
|
||||
.Where(x => x.StartsWith("_content_variant_") && x.EndsWith("_"))
|
||||
.Select(x => x.TrimStart("_content_variant_").TrimEnd("_"))
|
||||
.Select(x =>
|
||||
{
|
||||
// Format "<culture>_<segment>"
|
||||
var cs = x.Split(new[] { '_' });
|
||||
return (culture: cs[0], segment: cs[1]);
|
||||
})
|
||||
.Where(x => !x.culture.IsNullOrWhiteSpace())
|
||||
//if it's marked "invariant" than return the default language, this is because we can only edit invariant properties on the default language
|
||||
//so errors for those must show up under the default lang.
|
||||
//if the segment is marked "null" then return an actual null
|
||||
.Select(x =>
|
||||
{
|
||||
var culture = x.culture == "invariant" ? cultureForInvariantErrors : x.culture;
|
||||
var segment = x.segment == "null" ? null : x.segment;
|
||||
return (culture, segment);
|
||||
})
|
||||
.Distinct();
|
||||
|
||||
return propertyVariantErrors?.Union(genericVariantErrors).Distinct().ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the error to model state correctly for a property so we can use it on the client side.
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="result"></param>
|
||||
/// <param name="parts">
|
||||
/// Each model state validation error has a name and in most cases this name is made up of parts which are delimited by a '.'
|
||||
/// </param>
|
||||
internal static void AddValidationError(this ModelStateDictionary modelState,
|
||||
ValidationResult result, params string[] parts)
|
||||
{
|
||||
// if there are assigned member names, we combine the member name with the owner name
|
||||
// so that we can try to match it up to a real field. otherwise, we assume that the
|
||||
// validation message is for the overall owner.
|
||||
// Owner = the component being validated, like a content property but could be just an HTML field on another editor
|
||||
|
||||
var withNames = false;
|
||||
var delimitedParts = string.Join(".", parts);
|
||||
foreach (var memberName in result.MemberNames)
|
||||
{
|
||||
modelState.TryAddModelError($"{delimitedParts}.{memberName}", result.ErrorMessage ?? string.Empty);
|
||||
withNames = true;
|
||||
}
|
||||
if (!withNames)
|
||||
{
|
||||
modelState.TryAddModelError($"{delimitedParts}", result.ToString());
|
||||
}
|
||||
|
||||
modelState.TryAddModelError($"{delimitedParts}", result.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,110 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Umbraco.Cms.Core.Configuration.Models;
|
||||
using Umbraco.Cms.Core.Hosting;
|
||||
using Umbraco.Cms.Core.Manifest;
|
||||
using Umbraco.Cms.Core.WebAssets;
|
||||
using Umbraco.Cms.Infrastructure.WebAssets;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
public static class RuntimeMinifierExtensions
|
||||
{
|
||||
public static class RuntimeMinifierExtensions
|
||||
/// <summary>
|
||||
/// Returns the JavaScript to load the back office's assets
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> GetScriptForLoadingBackOfficeAsync(
|
||||
this IRuntimeMinifier minifier,
|
||||
GlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IManifestParser manifestParser)
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the JavaScript to load the back office's assets
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static async Task<string> GetScriptForLoadingBackOfficeAsync(
|
||||
this IRuntimeMinifier minifier,
|
||||
GlobalSettings globalSettings,
|
||||
IHostingEnvironment hostingEnvironment,
|
||||
IManifestParser manifestParser)
|
||||
var files = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach (var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoCoreJsBundleName))
|
||||
{
|
||||
var files = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach(var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoCoreJsBundleName))
|
||||
{
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
foreach (var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoExtensionsJsBundleName))
|
||||
{
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
// process the independent bundles
|
||||
if (manifestParser.CombinedManifest.Scripts.TryGetValue(BundleOptions.Independent, out IReadOnlyList<ManifestAssets>? independentManifestAssetsList))
|
||||
{
|
||||
foreach (ManifestAssets manifestAssets in independentManifestAssetsList)
|
||||
{
|
||||
var bundleName = BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Javascript);
|
||||
foreach(var asset in await minifier.GetJsAssetPathsAsync(bundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process the "None" bundles, meaning we'll just render the script as-is
|
||||
foreach (var asset in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoNonOptimizedPackageJsBundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
|
||||
var result = BackOfficeJavaScriptInitializer.GetJavascriptInitialization(
|
||||
files,
|
||||
"umbraco",
|
||||
globalSettings,
|
||||
hostingEnvironment);
|
||||
|
||||
result += await GetStylesheetInitializationAsync(minifier, manifestParser);
|
||||
|
||||
return result;
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the back office css bundle paths and formats a JS call to lazy load them
|
||||
/// </summary>
|
||||
private static async Task<string> GetStylesheetInitializationAsync(
|
||||
IRuntimeMinifier minifier,
|
||||
IManifestParser manifestParser)
|
||||
foreach (var file in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets.UmbracoExtensionsJsBundleName))
|
||||
{
|
||||
var files = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach(var file in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoCssBundleName))
|
||||
{
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
// process the independent bundles
|
||||
if (manifestParser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.Independent, out IReadOnlyList<ManifestAssets>? independentManifestAssetsList))
|
||||
{
|
||||
foreach (ManifestAssets manifestAssets in independentManifestAssetsList)
|
||||
{
|
||||
var bundleName = BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Css);
|
||||
foreach (var asset in await minifier.GetCssAssetPathsAsync(bundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process the "None" bundles, meaning we'll just render the script as-is
|
||||
foreach (var asset in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoNonOptimizedPackageCssBundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (string file in files)
|
||||
{
|
||||
sb.AppendFormat("{0}LazyLoad.css('{1}');", Environment.NewLine, file);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
// process the independent bundles
|
||||
if (manifestParser.CombinedManifest.Scripts.TryGetValue(BundleOptions.Independent,
|
||||
out IReadOnlyList<ManifestAssets>? independentManifestAssetsList))
|
||||
{
|
||||
foreach (ManifestAssets manifestAssets in independentManifestAssetsList)
|
||||
{
|
||||
var bundleName =
|
||||
BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Javascript);
|
||||
foreach (var asset in await minifier.GetJsAssetPathsAsync(bundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process the "None" bundles, meaning we'll just render the script as-is
|
||||
foreach (var asset in await minifier.GetJsAssetPathsAsync(BackOfficeWebAssets
|
||||
.UmbracoNonOptimizedPackageJsBundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
|
||||
var result = BackOfficeJavaScriptInitializer.GetJavascriptInitialization(
|
||||
files,
|
||||
"umbraco",
|
||||
globalSettings,
|
||||
hostingEnvironment);
|
||||
|
||||
result += await GetStylesheetInitializationAsync(minifier, manifestParser);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the back office css bundle paths and formats a JS call to lazy load them
|
||||
/// </summary>
|
||||
private static async Task<string> GetStylesheetInitializationAsync(
|
||||
IRuntimeMinifier minifier,
|
||||
IManifestParser manifestParser)
|
||||
{
|
||||
var files = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach (var file in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets.UmbracoCssBundleName))
|
||||
{
|
||||
files.Add(file);
|
||||
}
|
||||
|
||||
// process the independent bundles
|
||||
if (manifestParser.CombinedManifest.Stylesheets.TryGetValue(BundleOptions.Independent,
|
||||
out IReadOnlyList<ManifestAssets>? independentManifestAssetsList))
|
||||
{
|
||||
foreach (ManifestAssets manifestAssets in independentManifestAssetsList)
|
||||
{
|
||||
var bundleName = BackOfficeWebAssets.GetIndependentPackageBundleName(manifestAssets, AssetType.Css);
|
||||
foreach (var asset in await minifier.GetCssAssetPathsAsync(bundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process the "None" bundles, meaning we'll just render the script as-is
|
||||
foreach (var asset in await minifier.GetCssAssetPathsAsync(BackOfficeWebAssets
|
||||
.UmbracoNonOptimizedPackageCssBundleName))
|
||||
{
|
||||
files.Add(asset);
|
||||
}
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var file in files)
|
||||
{
|
||||
sb.AppendFormat("{0}LazyLoad.css('{1}');", Environment.NewLine, file);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
@@ -8,51 +7,50 @@ using Umbraco.Cms.Web.BackOffice.Middleware;
|
||||
using Umbraco.Cms.Web.BackOffice.Routing;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
/// <summary>
|
||||
/// <see cref="IUmbracoEndpointBuilderContext" /> extensions for Umbraco
|
||||
/// </summary>
|
||||
public static partial class UmbracoApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IUmbracoEndpointBuilderContext"/> extensions for Umbraco
|
||||
/// Adds all required middleware to run the back office
|
||||
/// </summary>
|
||||
public static partial class UmbracoApplicationBuilderExtensions
|
||||
/// <param name="builder"></param>
|
||||
/// <returns></returns>
|
||||
public static IUmbracoApplicationBuilderContext UseBackOffice(this IUmbracoApplicationBuilderContext builder)
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds all required middleware to run the back office
|
||||
/// </summary>
|
||||
/// <param name="builder"></param>
|
||||
/// <returns></returns>
|
||||
public static IUmbracoApplicationBuilderContext UseBackOffice(this IUmbracoApplicationBuilderContext builder)
|
||||
{
|
||||
KeepAliveSettings keepAliveSettings = builder.ApplicationServices.GetRequiredService<IOptions<KeepAliveSettings>>().Value;
|
||||
IHostingEnvironment hostingEnvironment = builder.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
||||
builder.AppBuilder.Map(
|
||||
hostingEnvironment.ToAbsolute(keepAliveSettings.KeepAlivePingUrl),
|
||||
a => a.UseMiddleware<KeepAliveMiddleware>());
|
||||
KeepAliveSettings keepAliveSettings =
|
||||
builder.ApplicationServices.GetRequiredService<IOptions<KeepAliveSettings>>().Value;
|
||||
IHostingEnvironment hostingEnvironment = builder.ApplicationServices.GetRequiredService<IHostingEnvironment>();
|
||||
builder.AppBuilder.Map(
|
||||
hostingEnvironment.ToAbsolute(keepAliveSettings.KeepAlivePingUrl),
|
||||
a => a.UseMiddleware<KeepAliveMiddleware>());
|
||||
|
||||
builder.AppBuilder.UseMiddleware<BackOfficeExternalLoginProviderErrorMiddleware>();
|
||||
return builder;
|
||||
builder.AppBuilder.UseMiddleware<BackOfficeExternalLoginProviderErrorMiddleware>();
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static IUmbracoEndpointBuilderContext UseBackOfficeEndpoints(this IUmbracoEndpointBuilderContext app)
|
||||
{
|
||||
// NOTE: This method will have been called after UseRouting, UseAuthentication, UseAuthorization
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
public static IUmbracoEndpointBuilderContext UseBackOfficeEndpoints(this IUmbracoEndpointBuilderContext app)
|
||||
if (!app.RuntimeState.UmbracoCanBoot())
|
||||
{
|
||||
// NOTE: This method will have been called after UseRouting, UseAuthentication, UseAuthorization
|
||||
if (app == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(app));
|
||||
}
|
||||
|
||||
if (!app.RuntimeState.UmbracoCanBoot())
|
||||
{
|
||||
return app;
|
||||
}
|
||||
|
||||
BackOfficeAreaRoutes backOfficeRoutes = app.ApplicationServices.GetRequiredService<BackOfficeAreaRoutes>();
|
||||
backOfficeRoutes.CreateRoutes(app.EndpointRouteBuilder);
|
||||
|
||||
app.UseUmbracoRuntimeMinificationEndpoints();
|
||||
app.UseUmbracoPreviewEndpoints();
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
BackOfficeAreaRoutes backOfficeRoutes = app.ApplicationServices.GetRequiredService<BackOfficeAreaRoutes>();
|
||||
backOfficeRoutes.CreateRoutes(app.EndpointRouteBuilder);
|
||||
|
||||
app.UseUmbracoRuntimeMinificationEndpoints();
|
||||
app.UseUmbracoPreviewEndpoints();
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,27 +3,26 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Web.BackOffice.Install;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="IApplicationBuilder" /> extensions for Umbraco installer
|
||||
/// </summary>
|
||||
public static partial class UmbracoApplicationBuilderExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IApplicationBuilder"/> extensions for Umbraco installer
|
||||
/// Enables the Umbraco installer
|
||||
/// </summary>
|
||||
public static partial class UmbracoApplicationBuilderExtensions
|
||||
public static IUmbracoEndpointBuilderContext UseInstallerEndpoints(this IUmbracoEndpointBuilderContext app)
|
||||
{
|
||||
/// <summary>
|
||||
/// Enables the Umbraco installer
|
||||
/// </summary>
|
||||
public static IUmbracoEndpointBuilderContext UseInstallerEndpoints(this IUmbracoEndpointBuilderContext app)
|
||||
if (!app.RuntimeState.UmbracoCanBoot())
|
||||
{
|
||||
if (!app.RuntimeState.UmbracoCanBoot())
|
||||
{
|
||||
return app;
|
||||
}
|
||||
|
||||
InstallAreaRoutes installerRoutes = app.ApplicationServices.GetRequiredService<InstallAreaRoutes>();
|
||||
installerRoutes.CreateRoutes(app.EndpointRouteBuilder);
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
InstallAreaRoutes installerRoutes = app.ApplicationServices.GetRequiredService<InstallAreaRoutes>();
|
||||
installerRoutes.CreateRoutes(app.EndpointRouteBuilder);
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Umbraco.Cms.Web.BackOffice.Routing;
|
||||
using Umbraco.Cms.Web.Common.ApplicationBuilder;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="IUmbracoEndpointBuilderContext"/> extensions for Umbraco
|
||||
/// </summary>
|
||||
public static partial class UmbracoApplicationBuilderExtensions
|
||||
{
|
||||
public static IUmbracoEndpointBuilderContext UseUmbracoPreviewEndpoints(this IUmbracoEndpointBuilderContext app)
|
||||
{
|
||||
PreviewRoutes previewRoutes = app.ApplicationServices.GetRequiredService<PreviewRoutes>();
|
||||
previewRoutes.CreateRoutes(app.EndpointRouteBuilder);
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
return app;
|
||||
}
|
||||
/// <summary>
|
||||
/// <see cref="IUmbracoEndpointBuilderContext" /> extensions for Umbraco
|
||||
/// </summary>
|
||||
public static partial class UmbracoApplicationBuilderExtensions
|
||||
{
|
||||
public static IUmbracoEndpointBuilderContext UseUmbracoPreviewEndpoints(this IUmbracoEndpointBuilderContext app)
|
||||
{
|
||||
PreviewRoutes previewRoutes = app.ApplicationServices.GetRequiredService<PreviewRoutes>();
|
||||
previewRoutes.CreateRoutes(app.EndpointRouteBuilder);
|
||||
|
||||
return app;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,19 @@ using Umbraco.Cms.Core.DependencyInjection;
|
||||
using Umbraco.Cms.Core.Mapping;
|
||||
using Umbraco.Cms.Web.BackOffice.Mapping;
|
||||
|
||||
namespace Umbraco.Extensions
|
||||
namespace Umbraco.Extensions;
|
||||
|
||||
public static class WebMappingProfiles
|
||||
{
|
||||
public static class WebMappingProfiles
|
||||
public static IUmbracoBuilder AddWebMappingProfiles(this IUmbracoBuilder builder)
|
||||
{
|
||||
public static IUmbracoBuilder AddWebMappingProfiles(this IUmbracoBuilder builder)
|
||||
{
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
|
||||
.Add<ContentMapDefinition>()
|
||||
.Add<MediaMapDefinition>()
|
||||
.Add<MemberMapDefinition>();
|
||||
builder.WithCollectionBuilder<MapDefinitionCollectionBuilder>()
|
||||
.Add<ContentMapDefinition>()
|
||||
.Add<MediaMapDefinition>()
|
||||
.Add<MemberMapDefinition>();
|
||||
|
||||
builder.Services.AddTransient<CommonTreeNodeMapper>();
|
||||
builder.Services.AddTransient<CommonTreeNodeMapper>();
|
||||
|
||||
return builder;
|
||||
}
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user