Merge remote-tracking branch 'origin/netcore/netcore' into netcore/feature/migrate-logging

# Conflicts:
#	src/Umbraco.Web.BackOffice/Runtime/BackOfficeComposer.cs
#	src/Umbraco.Web/Editors/BackOfficeController.cs
This commit is contained in:
Mole
2020-09-22 13:49:25 +02:00
18 changed files with 195 additions and 138 deletions

View File

@@ -1,7 +1,4 @@
using System.Collections.Generic;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Hosting;
using Umbraco.Core.Models;
using Umbraco.Web.Features;
@@ -10,7 +7,7 @@ namespace Umbraco.Web.Editors
public class BackOfficePreviewModel
{
private readonly UmbracoFeatures _features;
public BackOfficePreviewModel(UmbracoFeatures features, IEnumerable<ILanguage> languages)
{
_features = features;

View File

@@ -1,4 +1,4 @@
namespace Umbraco.Web.Models
namespace Umbraco.Core.Models
{
public class IconModel
{

View File

@@ -0,0 +1,22 @@
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Web.Models;
namespace Umbraco.Core.Services
{
public interface IIconService
{
/// <summary>
/// Gets an IconModel containing the icon name and SvgString according to an icon name found at the global icons path
/// </summary>
/// <param name="iconName"></param>
/// <returns></returns>
IconModel GetIcon(string iconName);
/// <summary>
/// Gets a list of all svg icons found at at the global icons path.
/// </summary>
/// <returns></returns>
IList<IconModel> GetAllIcons();
}
}

View File

@@ -1,31 +1,19 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Web.Models;
using System.IO;
using Umbraco.Core;
using Ganss.XSS;
using Microsoft.Extensions.Options;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Web.Common.Attributes;
using Umbraco.Web.Common.Filters;
namespace Umbraco.Web.Editors
{
[PluginController("UmbracoApi")]
public class IconController : UmbracoAuthorizedApiController
{
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IOptions<GlobalSettings> _globalSettings;
private readonly IIconService _iconService;
public IconController(
IHostingEnvironment hostingEnvironment,
IOptions<GlobalSettings> globalSettings)
public IconController(IIconService iconService)
{
_hostingEnvironment = hostingEnvironment;
_globalSettings = globalSettings;
_iconService = iconService;
}
/// <summary>
@@ -36,78 +24,16 @@ namespace Umbraco.Web.Editors
[DetermineAmbiguousActionByPassingParameters]
public IconModel GetIcon(string iconName)
{
return string.IsNullOrWhiteSpace(iconName)
? null
: CreateIconModel(iconName.StripFileExtension(),
_hostingEnvironment.MapPathWebRoot($"{_globalSettings.Value.IconsPath}/{iconName}.svg"));
}
/// <summary>
/// Gets an IconModel using values from a FileInfo model
/// </summary>
/// <param name="fileInfo"></param>
/// <returns></returns>
[DetermineAmbiguousActionByPassingParameters]
public IconModel GetIcon(FileInfo fileInfo)
{
return fileInfo == null || string.IsNullOrWhiteSpace(fileInfo.Name)
? null
: CreateIconModel(fileInfo.Name.StripFileExtension(), fileInfo.FullName);
return _iconService.GetIcon(iconName);
}
/// <summary>
/// Gets a list of all svg icons found at at the global icons path.
/// </summary>
/// <returns></returns>
public List<IconModel> GetAllIcons()
public IList<IconModel> GetAllIcons()
{
var icons = new List<IconModel>();
var directory = new DirectoryInfo(_hostingEnvironment.MapPathWebRoot($"{_globalSettings.Value.IconsPath}/"));
var iconNames = directory.GetFiles("*.svg");
iconNames.OrderBy(f => f.Name).ToList().ForEach(iconInfo =>
{
var icon = GetIcon(iconInfo);
if (icon != null)
{
icons.Add(icon);
}
});
return icons;
}
/// <summary>
/// Gets an IconModel containing the icon name and SvgString
/// </summary>
/// <param name="iconName"></param>
/// <param name="iconPath"></param>
/// <returns></returns>
private IconModel CreateIconModel(string iconName, string iconPath)
{
var sanitizer = new HtmlSanitizer();
sanitizer.AllowedAttributes.UnionWith(Core.Constants.SvgSanitizer.Attributes);
sanitizer.AllowedCssProperties.UnionWith(Core.Constants.SvgSanitizer.Attributes);
sanitizer.AllowedTags.UnionWith(Core.Constants.SvgSanitizer.Tags);
try
{
var svgContent = System.IO.File.ReadAllText(iconPath);
var sanitizedString = sanitizer.Sanitize(svgContent);
var svg = new IconModel
{
Name = iconName,
SvgString = sanitizedString
};
return svg;
}
catch
{
return null;
}
return _iconService.GetAllIcons();
}
}
}

View File

@@ -21,6 +21,8 @@ using Umbraco.Web.Editors;
using Umbraco.Web.Features;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Security;
using Umbraco.Web.Services;
using Umbraco.Web.Trees;
using Umbraco.Web.WebAssets;
using Constants = Umbraco.Core.Constants;

View File

@@ -4,10 +4,12 @@ using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Hosting;
using Umbraco.Core.IO;
using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Web.BackOffice.Controllers;
using Umbraco.Web.BackOffice.Routing;
using Umbraco.Web.BackOffice.Security;
using Umbraco.Web.BackOffice.Services;
using Umbraco.Web.BackOffice.Trees;
using Umbraco.Web.Common.Runtime;
using Umbraco.Web.Trees;
@@ -45,6 +47,8 @@ namespace Umbraco.Web.BackOffice.Runtime
factory.GetInstance<IHostingEnvironment>(),
factory.GetInstance<ILogger<PhysicalFileSystem>>(),
"~/"));
composition.RegisterUnique<IIconService, IconService>();
}
}
}

View File

@@ -0,0 +1,99 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Ganss.XSS;
using Microsoft.Extensions.Options;
using Umbraco.Core;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.Hosting;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using File = System.IO.File;
namespace Umbraco.Web.BackOffice.Services
{
public class IconService : IIconService
{
private readonly IOptions<GlobalSettings> _globalSettings;
private readonly IHostingEnvironment _hostingEnvironment;
public IconService(IOptions<GlobalSettings> globalSettings, IHostingEnvironment hostingEnvironment)
{
_globalSettings = globalSettings;
_hostingEnvironment = hostingEnvironment;
}
/// <inheritdoc />
public IList<IconModel> GetAllIcons()
{
var icons = new List<IconModel>();
var directory = new DirectoryInfo(_hostingEnvironment.MapPathWebRoot($"{_globalSettings.Value.IconsPath}/"));
var iconNames = directory.GetFiles("*.svg");
iconNames.OrderBy(f => f.Name).ToList().ForEach(iconInfo =>
{
var icon = GetIcon(iconInfo);
if (icon != null)
{
icons.Add(icon);
}
});
return icons;
}
/// <inheritdoc />
public IconModel GetIcon(string iconName)
{
return string.IsNullOrWhiteSpace(iconName)
? null
: CreateIconModel(iconName.StripFileExtension(), _hostingEnvironment.MapPathWebRoot($"{_globalSettings.Value.IconsPath}/{iconName}.svg"));
}
/// <summary>
/// Gets an IconModel using values from a FileInfo model
/// </summary>
/// <param name="fileInfo"></param>
/// <returns></returns>
private IconModel GetIcon(FileInfo fileInfo)
{
return fileInfo == null || string.IsNullOrWhiteSpace(fileInfo.Name)
? null
: CreateIconModel(fileInfo.Name.StripFileExtension(), fileInfo.FullName);
}
/// <summary>
/// Gets an IconModel containing the icon name and SvgString
/// </summary>
/// <param name="iconName"></param>
/// <param name="iconPath"></param>
/// <returns></returns>
private IconModel CreateIconModel(string iconName, string iconPath)
{
var sanitizer = new HtmlSanitizer();
sanitizer.AllowedAttributes.UnionWith(Core.Constants.SvgSanitizer.Attributes);
sanitizer.AllowedCssProperties.UnionWith(Core.Constants.SvgSanitizer.Attributes);
sanitizer.AllowedTags.UnionWith(Core.Constants.SvgSanitizer.Tags);
try
{
var svgContent = File.ReadAllText(iconPath);
var sanitizedString = sanitizer.Sanitize(svgContent);
var svg = new IconModel
{
Name = iconName,
SvgString = sanitizedString
};
return svg;
}
catch
{
return null;
}
}
}
}

View File

@@ -9,6 +9,7 @@ using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Runtime;
using Umbraco.Core.Security;
using Umbraco.Core.Services;
using Umbraco.Extensions;
using Umbraco.Net;
using Umbraco.Web.Common.AspNetCore;
@@ -99,8 +100,6 @@ namespace Umbraco.Web.Common.Runtime
composition.RegisterUnique<ITemplateRenderer, TemplateRenderer>();
composition.RegisterUnique<IPublicAccessChecker, PublicAccessChecker>();
composition.RegisterUnique(factory => new LegacyPasswordSecurity(factory.GetInstance<IOptions<UserPasswordConfigurationSettings>>().Value));
}
}
}

View File

@@ -31,7 +31,7 @@ function iconHelper($http, $q, $sce, $timeout, umbRequestHelper) {
{ oldIcon: ".sprToPublish", newIcon: "mail-forward" },
{ oldIcon: ".sprTranslate", newIcon: "comments" },
{ oldIcon: ".sprUpdate", newIcon: "save" },
{ oldIcon: ".sprTreeSettingDomain", newIcon: "icon-home" },
{ oldIcon: ".sprTreeDoc", newIcon: "icon-document" },
{ oldIcon: ".sprTreeDoc2", newIcon: "icon-diploma-alt" },
@@ -39,21 +39,21 @@ function iconHelper($http, $q, $sce, $timeout, umbRequestHelper) {
{ oldIcon: ".sprTreeDoc4", newIcon: "icon-newspaper-alt" },
{ oldIcon: ".sprTreeDoc5", newIcon: "icon-notepad-alt" },
{ oldIcon: ".sprTreeDocPic", newIcon: "icon-picture" },
{ oldIcon: ".sprTreeDocPic", newIcon: "icon-picture" },
{ oldIcon: ".sprTreeFolder", newIcon: "icon-folder" },
{ oldIcon: ".sprTreeFolder_o", newIcon: "icon-folder" },
{ oldIcon: ".sprTreeMediaFile", newIcon: "icon-music" },
{ oldIcon: ".sprTreeMediaMovie", newIcon: "icon-movie" },
{ oldIcon: ".sprTreeMediaPhoto", newIcon: "icon-picture" },
{ oldIcon: ".sprTreeMember", newIcon: "icon-user" },
{ oldIcon: ".sprTreeMemberGroup", newIcon: "icon-users" },
{ oldIcon: ".sprTreeMemberType", newIcon: "icon-users" },
{ oldIcon: ".sprTreeNewsletter", newIcon: "icon-file-text-alt" },
{ oldIcon: ".sprTreePackage", newIcon: "icon-box" },
{ oldIcon: ".sprTreeRepository", newIcon: "icon-server-alt" },
{ oldIcon: ".sprTreeSettingDataType", newIcon: "icon-autofill" },
// TODO: Something needs to be done with the old tree icons that are commented out.
@@ -61,7 +61,7 @@ function iconHelper($http, $q, $sce, $timeout, umbRequestHelper) {
{ oldIcon: ".sprTreeSettingAgent", newIcon: "" },
{ oldIcon: ".sprTreeSettingCss", newIcon: "" },
{ oldIcon: ".sprTreeSettingCssItem", newIcon: "" },
{ oldIcon: ".sprTreeSettingDataTypeChild", newIcon: "" },
{ oldIcon: ".sprTreeSettingDomain", newIcon: "" },
{ oldIcon: ".sprTreeSettingLanguage", newIcon: "" },
@@ -94,9 +94,9 @@ function iconHelper($http, $q, $sce, $timeout, umbRequestHelper) {
var iconCache = [];
var liveRequests = [];
var allIconsRequested = false;
return {
/** Used by the create dialogs for content/media types to format the data so that the thumbnails are styled properly */
formatContentTypeThumbnails: function (contentTypes) {
for (var i = 0; i < contentTypes.length; i++) {
@@ -209,15 +209,11 @@ function iconHelper($http, $q, $sce, $timeout, umbRequestHelper) {
,'Failed to retrieve icon: ' + iconName)
.then(icon => {
if(icon) {
var trustedIcon = {
name: icon.Name,
svgString: $sce.trustAsHtml(icon.SvgString)
};
this._cacheIcon(trustedIcon);
var trustedIcon = this.defineIcon(icon.Name, icon.SvgString);
liveRequests = _.filter(liveRequests, iconRequestPath);
liveRequests = _.filter(liveRequests, iconRequestPath);
resolve(trustedIcon);
resolve(trustedIcon);
}
})
.catch(err => {
@@ -240,15 +236,10 @@ function iconHelper($http, $q, $sce, $timeout, umbRequestHelper) {
,'Failed to retrieve icons')
.then(icons => {
icons.forEach(icon => {
var trustedIcon = {
name: icon.Name,
svgString: $sce.trustAsHtml(icon.SvgString)
};
this._cacheIcon(trustedIcon);
this.defineIcon(icon.Name, icon.SvgString);
});
resolve(iconCache);
resolve(iconCache);
})
.catch(err => {
console.warn(err);
@@ -278,7 +269,7 @@ function iconHelper($http, $q, $sce, $timeout, umbRequestHelper) {
console.warn("Can't read the css rules of: " + document.styleSheets[i].href, e);
continue;
}
if (classes !== null) {
for(var x=0;x<classes.length;x++) {
var cur = classes[x];
@@ -312,11 +303,17 @@ function iconHelper($http, $q, $sce, $timeout, umbRequestHelper) {
return deferred.promise;
},
/** A simple cache to ensure that the icon is only requested from the server if is isn't already in memory */
_cacheIcon: function(icon) {
if(_.find(iconCache, {name: icon.name}) === undefined) {
iconCache = _.union(iconCache, [icon]);
}
/** Creates a icon object, and caches it in a runtime cache */
defineIcon: function(name, svg) {
var icon = iconCache.find(x => x.name === name);
if(icon === undefined) {
icon = {
name: name,
svgString: $sce.trustAsHtml(svg)
};
iconCache.push(icon);
}
return icon;
},
/** Returns the cached icon or undefined */

View File

@@ -6,6 +6,7 @@
vm.submit = submit;
vm.close = close;
vm.cropSet = cropSet;
if (!$scope.model.target.coordinates && !$scope.model.target.focalPoint) {
$scope.model.target.focalPoint = { left: .5, top: .5 };
@@ -56,4 +57,8 @@
}
}
function cropSet() {
var model = $scope.model;
return (model.cropSize || {}).width && model.target.thumbnail;
}
});

View File

@@ -26,7 +26,7 @@
<div class="umb-control-group" ng-if="model.target">
<div ng-if="model.disableFocalPoint && model.target.thumbnail">
<div ng-if="vm.cropSet() === false">
<h5>
<localize key="general_preview">Preview</localize>
</h5>
@@ -34,7 +34,7 @@
<img ng-src="{{model.target.thumbnail}}" alt="{{model.target.name}}" />
</div>
<div ng-if="!model.disableFocalPoint">
<div ng-if="vm.cropSet()">
<h5>
<localize key="@general_cropSection">Crop section</localize>
</h5>

View File

@@ -21,7 +21,7 @@
<!-- Renders when it's only possible to create one specific document type for which a blueprint exits-->
<div class="btn-group" ng-show="createAllowedButtonSingleWithBlueprints" deep-blur="leaveDropdown()">
<button type="button" class="btn btn-white dropdown-toggle" aria-expanded="{{page.createDropdownOpen}}" ng-click="toggleDropdown()">
<button type="button" class="btn btn-outline umb-outline dropdown-toggle" aria-expanded="{{page.createDropdownOpen}}" ng-click="toggleDropdown()">
<span>
<localize key="actions_create">Create</localize> {{listViewAllowedTypes[0].name}}
</span>
@@ -45,7 +45,7 @@
<!-- Renders when it's possible to create multiple document types and blueprints for one or more of the document types-->
<div class="btn-group" ng-show="createAllowedButtonMultiWithBlueprints" deep-blur="leaveDropdown()">
<button type="button" class="btn btn-white dropdown-toggle" aria-expanded="{{page.createDropdownOpen === undefined ? false : page.createDropdownOpen}}" ng-click="toggleDropdown()">
<button type="button" class="btn btn-outline umb-outline dropdown-toggle" aria-expanded="{{page.createDropdownOpen === undefined ? false : page.createDropdownOpen}}" ng-click="toggleDropdown()">
<localize key="actions_create">Create</localize>
<span class="caret" aria-hidden="true"></span>
</button>

View File

@@ -8,6 +8,7 @@
@using Umbraco.Core.Hosting
@using Umbraco.Extensions
@using Umbraco.Core.Logging
@using Umbraco.Core.Services
@using Umbraco.Web.BackOffice.Controllers
@inject BackOfficeSignInManager signInManager
@inject BackOfficeServerVariables backOfficeServerVariables
@@ -16,7 +17,7 @@
@inject IOptions<GlobalSettings> globalSettings
@inject IRuntimeMinifier runtimeMinifier
@inject IProfilerHtml profilerHtml
@inject IIconService IconService
@{
bool.TryParse(Context.Request.Query["umbDebug"], out bool isDebug);
var backOfficePath = globalSettings.Value.GetBackOfficePath(hostingEnvironment);
@@ -111,6 +112,13 @@
@await Html.AngularValueExternalLoginInfoScriptAsync(signInManager, ViewData.GetExternalSignInError())
@Html.AngularValueResetPasswordCodeInfoScript(ViewData["PasswordResetCode"])
@await Html.AngularValueTinyMceAssetsAsync(runtimeMinifier)
app.run(["iconHelper", function (iconHelper) {
@* We inject icons to the icon helper(service), since icons can only be loaded if user is authorized. By injecting these to the service they will not be requested as they will become cached. *@
iconHelper.defineIcon("icon-check", '@Html.Raw(IconService.GetIcon("icon-check")?.SvgString)');
iconHelper.defineIcon("icon-delete", '@Html.Raw(IconService.GetIcon("icon-delete")?.SvgString)');
}]);
//required for the noscript trick
document.getElementById("mainwrapper").style.display = "inherit";
}

View File

@@ -178,6 +178,9 @@ namespace Umbraco.Web.Composing
public static ISectionService SectionService
=> Factory.GetInstance<ISectionService>();
public static IIconService IconService
=> Factory.GetInstance<IIconService>();
#endregion
#region Web Constants

View File

@@ -11,6 +11,7 @@ using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Models;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Web.Mvc;
using Umbraco.Core.Services;
@@ -38,7 +39,8 @@ namespace Umbraco.Web.Editors
private readonly IHostingEnvironment _hostingEnvironment;
private readonly RuntimeSettings _runtimeSettings;
private readonly SecuritySettings _securitySettings;
private readonly Microsoft.Extensions.Logging.ILogger<BackOfficeController> _logger;
private readonly IIconService _iconService;
private readonly ILogger<BackOfficeController> _logger;
public BackOfficeController(
UmbracoFeatures features,
@@ -52,7 +54,8 @@ namespace Umbraco.Web.Editors
IOptions<ContentSettings> contentSettings,
IHostingEnvironment hostingEnvironment,
IOptions<RuntimeSettings> settings,
IOptions<SecuritySettings> securitySettings)
IOptions<SecuritySettings> securitySettings,
IIconService iconService)
: base(globalSettings, umbracoContextAccessor, services, appCaches, profilingLogger, loggerFactory)
{
@@ -62,6 +65,7 @@ namespace Umbraco.Web.Editors
_hostingEnvironment = hostingEnvironment;
_runtimeSettings = settings.Value;
_securitySettings = securitySettings.Value;
_iconService = iconService;
_logger = loggerFactory.CreateLogger<BackOfficeController>();
}

View File

@@ -1,8 +0,0 @@
namespace Umbraco.Web.Models
{
public class IconModel
{
public string Name { get; set; }
public string SvgString { get; set; }
}
}

View File

@@ -1,5 +1,4 @@
using System.Linq;
using System.Web.Security;
using System.Web.Security;
using Microsoft.AspNet.SignalR;
using Umbraco.Core;
using Umbraco.Core.Composing;
@@ -15,8 +14,7 @@ using Umbraco.Web.PublishedCache;
using Umbraco.Web.Security;
using Umbraco.Web.Security.Providers;
using Umbraco.Web.SignalR;
using Umbraco.Web.Templates;
using Umbraco.Web.Trees;
using Umbraco.Web.Services;
namespace Umbraco.Web.Runtime
{

View File

@@ -172,7 +172,6 @@
<Compile Include="Logging\WebProfilerComposer.cs" />
<Compile Include="Logging\WebProfilerProvider.cs" />
<Compile Include="Models\Membership\UmbracoMembershipMember.cs" />
<Compile Include="Models\IconModel.cs" />
<Compile Include="Mvc\HttpUmbracoFormRouteStringException.cs" />
<Compile Include="Mvc\ModelBindingExceptionFilter.cs" />
<Compile Include="Mvc\SurfaceControllerTypeCollectionBuilder.cs" />
@@ -407,6 +406,8 @@
<Link>Mvc\web.config</Link>
</None>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="Services" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>