From 5a37854dd5d3412cc0ac18dfed003b212ac192c7 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 14 Sep 2020 12:02:26 +0100 Subject: [PATCH 01/15] Merge pull request #7599 from umbraco/v8/feature/ui-media-create-button-style UI: corrected button style to match new style (cherry picked from commit 2e055ca146a7ab1814ce232ccc97fa07571733df) --- .../src/views/propertyeditors/listview/listview.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html index 05a294cc1c..e72b81b019 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html @@ -21,7 +21,7 @@
- From 6d2135edd5b0c92560492ee6417d136740a0d07b Mon Sep 17 00:00:00 2001 From: Niels Swimberghe Date: Tue, 15 Sep 2020 12:54:00 -0400 Subject: [PATCH 02/15] Change wrong URL in KeepAlive XML Comments The XML comments for KeepAlive mention the URL for the KeepAlive.Ping action but incorrectly omits 'umbraco' from the path. The change adds the 'umbraco' section to the path. (cherry picked from commit 312dc1d9a757469daa209fe99f66abc5b0dc2309) --- src/Umbraco.Web.UI/config/umbracoSettings.config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 73a5143a20..d723b9c7c0 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -259,7 +259,7 @@ Defaults to "false". @keepAlivePingUrl The url of the KeepAlivePing action. By default, the url will use the umbracoApplicationUrl setting as the basis. - Change this setting to specify an alternative url to reach the KeepAlivePing action. eg http://localhost/api/keepalive/ping + Change this setting to specify an alternative url to reach the KeepAlivePing action. eg http://localhost/umbraco/api/keepalive/ping Defaults to "{umbracoApplicationUrl}/api/keepalive/ping". --> From 6d15a2fce3ba8e0585b5b6df45d1a274daf76a46 Mon Sep 17 00:00:00 2001 From: Lars-Erik Aabech Date: Thu, 17 Sep 2020 11:32:52 +0200 Subject: [PATCH 03/15] Eliminates error/no-preview when grid media picker have no crop set (#8620) (cherry picked from commit deb03e4d82e48674c6d8bdc7926e820e17454d9e) --- .../mediapicker/overlays/mediacropdetails.controller.js | 5 +++++ .../mediapicker/overlays/mediacropdetails.html | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js index d9ea5a7a09..030200e1e6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.controller.js @@ -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; + } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html index 3814ac851e..cda42043c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/overlays/mediacropdetails.html @@ -26,7 +26,7 @@
-
+
Preview
@@ -34,7 +34,7 @@ {{model.target.name}}
-
+
Crop section
From 6b0539fefcf2f7e103a38638c0328f2a418cb520 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 17 Sep 2020 12:19:07 +0200 Subject: [PATCH 04/15] Set version to RC --- src/SolutionInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 3f512bc65a..26496aa9e6 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -19,4 +19,4 @@ using System.Resources; // these are FYI and changed automatically [assembly: AssemblyFileVersion("8.8.0")] -[assembly: AssemblyInformationalVersion("8.8.0")] +[assembly: AssemblyInformationalVersion("8.8.0-rc")] From 0506c8c555e5398591afd84277e0f48a69673415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 17 Sep 2020 14:59:12 +0200 Subject: [PATCH 05/15] defineIcon method --- .../src/common/services/iconhelper.service.js | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js index 0d0135fff8..f714cba4ad 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js @@ -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 x.name === name); + if(icon === undefined) { + icon = { + name: name, + svgString: $sce.trustAsHtml(svg) + }; + iconCache.push(icon); + } + return icon; }, /** Returns the cached icon or undefined */ From e2957bb715d797299d5cc65c8d073fd17bb15441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Thu, 17 Sep 2020 14:59:49 +0200 Subject: [PATCH 06/15] inject icons to iconHelper to avoid making requests to the icon controller --- .../Umbraco/Views/Default.cshtml | 286 +++++++++--------- 1 file changed, 148 insertions(+), 138 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml index 616763c06f..d0f0fd2cc8 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml @@ -1,138 +1,148 @@ -@using Umbraco.Core -@using ClientDependency.Core -@using ClientDependency.Core.Mvc -@using Umbraco.Core.Composing -@using Umbraco.Core.IO -@using Umbraco.Web -@using Umbraco.Core.Configuration - -@inherits WebViewPage - -@{ - var isDebug = false; - if (Request.RawUrl.IndexOf('?') >= 0) - { - var parsed = HttpUtility.ParseQueryString(Request.RawUrl.Split('?')[1]); - var attempt = parsed["umbDebug"].TryConvertTo(); - if (attempt && attempt.Result) - { - isDebug = true; - } - } - - Html - .RequiresCss("assets/css/umbraco.css", "Umbraco") - .RequiresCss("lib/bootstrap-social/bootstrap-social.css", "Umbraco") - .RequiresCss("lib/font-awesome/css/font-awesome.min.css", "Umbraco"); -} - - - - - - - - - - - - - - Umbraco - - @Html.RenderCssHere( - new BasicPath("Umbraco", IOHelper.ResolveUrl(SystemDirectories.Umbraco))) - - - - - -
- - - - - - - - - - - -
- - - - - - - - - - - - - @Html.BareMinimumServerVariablesScript(Url, Url.Action("ExternalLogin", "BackOffice", new { area = ViewData.GetUmbracoPath() }), Model.Features, Current.Configs.Global()) - - - - - - - @if (isDebug) - { - @Html.RenderProfiler() - } - - - +@using Umbraco.Core +@using ClientDependency.Core +@using ClientDependency.Core.Mvc +@using Umbraco.Core.Composing +@using Umbraco.Core.IO +@using Umbraco.Web +@using Umbraco.Core.Configuration + +@inherits WebViewPage + +@{ + var isDebug = false; + if (Request.RawUrl.IndexOf('?') >= 0) + { + var parsed = HttpUtility.ParseQueryString(Request.RawUrl.Split('?')[1]); + var attempt = parsed["umbDebug"].TryConvertTo(); + if (attempt && attempt.Result) + { + isDebug = true; + } + } + + Html + .RequiresCss("assets/css/umbraco.css", "Umbraco") + .RequiresCss("lib/bootstrap-social/bootstrap-social.css", "Umbraco") + .RequiresCss("lib/font-awesome/css/font-awesome.min.css", "Umbraco"); +} + + + + + + + + + + + + + + Umbraco + + @Html.RenderCssHere( + new BasicPath("Umbraco", IOHelper.ResolveUrl(SystemDirectories.Umbraco))) + + + + + +
+ + + + + + + + + + + +
+ + + + + + + + + + + + + @Html.BareMinimumServerVariablesScript(Url, Url.Action("ExternalLogin", "BackOffice", new { area = ViewData.GetUmbracoPath() }), Model.Features, Current.Configs.Global()) + + + + + + + @if (isDebug) + { + @Html.RenderProfiler() + } + + + From 3950f3286e7aa647e0345db887a95dcef0af86f8 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 21 Sep 2020 11:27:47 +0100 Subject: [PATCH 07/15] Merge pull request #8815 from umbraco/v8/bugfix/events-unsubscribe Ensure event handlers are unsubscribed in Core Components (cherry picked from commit e7f98a569064c61436bf2dae695f68af9a13cdca) --- .../Compose/AuditEventsComponent.cs | 14 ++- .../Compose/RelateOnCopyComponent.cs | 4 +- .../Compose/RelateOnTrashComponent.cs | 7 +- .../Compose/ModelsBuilderComponent.cs | 44 ++++--- .../BackOfficeUserAuditEventsComponent.cs | 14 ++- .../Compose/NotificationsComponent.cs | 110 ++++++++++++------ .../Compose/PublicAccessComponent.cs | 4 +- .../Logging/WebProfilerComponent.cs | 16 ++- .../PropertyEditorsComponent.cs | 61 ++++++---- .../Routing/RedirectTrackingComponent.cs | 7 +- src/Umbraco.Web/Search/ExamineComponent.cs | 8 +- 11 files changed, 203 insertions(+), 86 deletions(-) diff --git a/src/Umbraco.Core/Compose/AuditEventsComponent.cs b/src/Umbraco.Core/Compose/AuditEventsComponent.cs index 033b46c13f..62610ca9c7 100644 --- a/src/Umbraco.Core/Compose/AuditEventsComponent.cs +++ b/src/Umbraco.Core/Compose/AuditEventsComponent.cs @@ -42,7 +42,19 @@ namespace Umbraco.Core.Compose } public void Terminate() - { } + { + UserService.SavedUserGroup -= OnSavedUserGroupWithUsers; + + UserService.SavedUser -= OnSavedUser; + UserService.DeletedUser -= OnDeletedUser; + UserService.UserGroupPermissionsAssigned -= UserGroupPermissionAssigned; + + MemberService.Saved -= OnSavedMember; + MemberService.Deleted -= OnDeletedMember; + MemberService.AssignedRoles -= OnAssignedRoles; + MemberService.RemovedRoles -= OnRemovedRoles; + MemberService.Exported -= OnMemberExported; + } internal static IUser UnknownUser => new User { Id = Constants.Security.UnknownUserId, Name = Constants.Security.UnknownUserName, Email = "" }; diff --git a/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs b/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs index b56ff8b87e..a9f9ed1ee0 100644 --- a/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs +++ b/src/Umbraco.Core/Compose/RelateOnCopyComponent.cs @@ -14,7 +14,9 @@ namespace Umbraco.Core.Compose } public void Terminate() - { } + { + ContentService.Copied -= ContentServiceCopied; + } private static void ContentServiceCopied(IContentService sender, Events.CopyEventArgs e) { diff --git a/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs index 4e01c50fc6..a216a584ba 100644 --- a/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs +++ b/src/Umbraco.Core/Compose/RelateOnTrashComponent.cs @@ -18,7 +18,12 @@ namespace Umbraco.Core.Compose } public void Terminate() - { } + { + ContentService.Moved -= ContentService_Moved; + ContentService.Trashed -= ContentService_Trashed; + MediaService.Moved -= MediaService_Moved; + MediaService.Trashed -= MediaService_Trashed; + } private static void ContentService_Moved(IContentService sender, MoveEventArgs e) { diff --git a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs index 0e41c9ac62..7434131dc2 100644 --- a/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs +++ b/src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComponent.cs @@ -50,32 +50,38 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose } public void Terminate() - { } + { + ServerVariablesParser.Parsing -= ServerVariablesParser_Parsing; + ContentModelBinder.ModelBindingException -= ContentModelBinder_ModelBindingException; + FileService.SavingTemplate -= FileService_SavingTemplate; + } private void InstallServerVars() { // register our url - for the backoffice api - ServerVariablesParser.Parsing += (sender, serverVars) => - { - if (!serverVars.ContainsKey("umbracoUrls")) - throw new ArgumentException("Missing umbracoUrls."); - var umbracoUrlsObject = serverVars["umbracoUrls"]; - if (umbracoUrlsObject == null) - throw new ArgumentException("Null umbracoUrls"); - if (!(umbracoUrlsObject is Dictionary umbracoUrls)) - throw new ArgumentException("Invalid umbracoUrls"); + ServerVariablesParser.Parsing += ServerVariablesParser_Parsing; + } - if (!serverVars.ContainsKey("umbracoPlugins")) - throw new ArgumentException("Missing umbracoPlugins."); - if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) - throw new ArgumentException("Invalid umbracoPlugins"); + private void ServerVariablesParser_Parsing(object sender, Dictionary serverVars) + { + if (!serverVars.ContainsKey("umbracoUrls")) + throw new ArgumentException("Missing umbracoUrls."); + var umbracoUrlsObject = serverVars["umbracoUrls"]; + if (umbracoUrlsObject == null) + throw new ArgumentException("Null umbracoUrls"); + if (!(umbracoUrlsObject is Dictionary umbracoUrls)) + throw new ArgumentException("Invalid umbracoUrls"); - if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); - var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + if (!serverVars.ContainsKey("umbracoPlugins")) + throw new ArgumentException("Missing umbracoPlugins."); + if (!(serverVars["umbracoPlugins"] is Dictionary umbracoPlugins)) + throw new ArgumentException("Invalid umbracoPlugins"); - umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); - umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); - }; + if (HttpContext.Current == null) throw new InvalidOperationException("HttpContext is null"); + var urlHelper = new UrlHelper(new RequestContext(new HttpContextWrapper(HttpContext.Current), new RouteData())); + + umbracoUrls["modelsBuilderBaseUrl"] = urlHelper.GetUmbracoApiServiceBaseUrl(controller => controller.BuildModels()); + umbracoPlugins["modelsBuilder"] = GetModelsBuilderSettings(); } private Dictionary GetModelsBuilderSettings() diff --git a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs index 4244d575af..d0341ec3b6 100644 --- a/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs +++ b/src/Umbraco.Web/Compose/BackOfficeUserAuditEventsComponent.cs @@ -35,7 +35,19 @@ namespace Umbraco.Web.Compose } public void Terminate() - { } + { + //BackOfficeUserManager.AccountLocked -= ; + //BackOfficeUserManager.AccountUnlocked -= ; + BackOfficeUserManager.ForgotPasswordRequested -= OnForgotPasswordRequest; + BackOfficeUserManager.ForgotPasswordChangedSuccess -= OnForgotPasswordChange; + BackOfficeUserManager.LoginFailed -= OnLoginFailed; + //BackOfficeUserManager.LoginRequiresVerification -= ; + BackOfficeUserManager.LoginSuccess -= OnLoginSuccess; + BackOfficeUserManager.LogoutSuccess -= OnLogoutSuccess; + BackOfficeUserManager.PasswordChanged -= OnPasswordChanged; + BackOfficeUserManager.PasswordReset -= OnPasswordReset; + //BackOfficeUserManager.ResetAccessFailedCount -= ; + } private IUser GetPerformingUser(int userId) { diff --git a/src/Umbraco.Web/Compose/NotificationsComponent.cs b/src/Umbraco.Web/Compose/NotificationsComponent.cs index 6fcc1269cb..24c7992dd2 100644 --- a/src/Umbraco.Web/Compose/NotificationsComponent.cs +++ b/src/Umbraco.Web/Compose/NotificationsComponent.cs @@ -32,42 +32,78 @@ namespace Umbraco.Web.Compose public void Initialize() { //Send notifications for the send to publish action - ContentService.SentToPublish += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Entity); - + ContentService.SentToPublish += ContentService_SentToPublish; //Send notifications for the published action - ContentService.Published += (sender, args) => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); - + ContentService.Published += ContentService_Published; //Send notifications for the saved action - ContentService.Sorted += (sender, args) => ContentServiceSorted(_notifier, sender, args, _actions); - + ContentService.Sorted += ContentService_Sorted; //Send notifications for the update and created actions - ContentService.Saved += (sender, args) => ContentServiceSaved(_notifier, sender, args, _actions); - + ContentService.Saved += ContentService_Saved; //Send notifications for the unpublish action - ContentService.Unpublished += (sender, args) => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); - + ContentService.Unpublished += ContentService_Unpublished; //Send notifications for the move/move to recycle bin and restore actions - ContentService.Moved += (sender, args) => ContentServiceMoved(_notifier, sender, args, _actions); - + ContentService.Moved += ContentService_Moved; //Send notifications for the delete action when content is moved to the recycle bin - ContentService.Trashed += (sender, args) => _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); - + ContentService.Trashed += ContentService_Trashed; //Send notifications for the copy action - ContentService.Copied += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Original); - + ContentService.Copied += ContentService_Copied; //Send notifications for the rollback action - ContentService.RolledBack += (sender, args) => _notifier.Notify(_actions.GetAction(), args.Entity); - + ContentService.RolledBack += ContentService_RolledBack; //Send notifications for the public access changed action - PublicAccessService.Saved += (sender, args) => PublicAccessServiceSaved(_notifier, sender, args, _contentService, _actions); - - UserService.UserGroupPermissionsAssigned += (sender, args) => UserServiceUserGroupPermissionsAssigned(_notifier, sender, args, _contentService, _actions); + PublicAccessService.Saved += PublicAccessService_Saved; + + UserService.UserGroupPermissionsAssigned += UserService_UserGroupPermissionsAssigned; } public void Terminate() - { } + { + ContentService.SentToPublish -= ContentService_SentToPublish; + ContentService.Published -= ContentService_Published; + ContentService.Sorted -= ContentService_Sorted; + ContentService.Saved -= ContentService_Saved; + ContentService.Unpublished -= ContentService_Unpublished; + ContentService.Moved -= ContentService_Moved; + ContentService.Trashed -= ContentService_Trashed; + ContentService.Copied -= ContentService_Copied; + ContentService.RolledBack -= ContentService_RolledBack; + PublicAccessService.Saved -= PublicAccessService_Saved; + UserService.UserGroupPermissionsAssigned -= UserService_UserGroupPermissionsAssigned; + } - private void ContentServiceSorted(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args, ActionCollection actions) + private void UserService_UserGroupPermissionsAssigned(IUserService sender, Core.Events.SaveEventArgs args) + => UserServiceUserGroupPermissionsAssigned(args, _contentService); + + private void PublicAccessService_Saved(IPublicAccessService sender, Core.Events.SaveEventArgs args) + => PublicAccessServiceSaved(args, _contentService); + + private void ContentService_RolledBack(IContentService sender, Core.Events.RollbackEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.Entity); + + private void ContentService_Copied(IContentService sender, Core.Events.CopyEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.Original); + + private void ContentService_Trashed(IContentService sender, Core.Events.MoveEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); + + private void ContentService_Moved(IContentService sender, Core.Events.MoveEventArgs args) + => ContentServiceMoved(args); + + private void ContentService_Unpublished(IContentService sender, Core.Events.PublishEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); + + private void ContentService_Saved(IContentService sender, Core.Events.ContentSavedEventArgs args) + => ContentServiceSaved(args); + + private void ContentService_Sorted(IContentService sender, Core.Events.SaveEventArgs args) + => ContentServiceSorted(sender, args); + + private void ContentService_Published(IContentService sender, Core.Events.ContentPublishedEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.PublishedEntities.ToArray()); + + private void ContentService_SentToPublish(IContentService sender, Core.Events.SendToPublishEventArgs args) + => _notifier.Notify(_actions.GetAction(), args.Entity); + + private void ContentServiceSorted(IContentService sender, Core.Events.SaveEventArgs args) { var parentId = args.SavedEntities.Select(x => x.ParentId).Distinct().ToList(); if (parentId.Count != 1) return; // this shouldn't happen, for sorting all entities will have the same parent id @@ -79,10 +115,10 @@ namespace Umbraco.Web.Compose var parent = sender.GetById(parentId[0]); if (parent == null) return; // this shouldn't happen - notifier.Notify(actions.GetAction(), new[] { parent }); + _notifier.Notify(_actions.GetAction(), new[] { parent }); } - private void ContentServiceSaved(Notifier notifier, IContentService sender, Core.Events.SaveEventArgs args, ActionCollection actions) + private void ContentServiceSaved(Core.Events.SaveEventArgs args) { var newEntities = new List(); var updatedEntities = new List(); @@ -102,21 +138,21 @@ namespace Umbraco.Web.Compose updatedEntities.Add(entity); } } - notifier.Notify(actions.GetAction(), newEntities.ToArray()); - notifier.Notify(actions.GetAction(), updatedEntities.ToArray()); + _notifier.Notify(_actions.GetAction(), newEntities.ToArray()); + _notifier.Notify(_actions.GetAction(), updatedEntities.ToArray()); } - private void UserServiceUserGroupPermissionsAssigned(Notifier notifier, IUserService sender, Core.Events.SaveEventArgs args, IContentService contentService, ActionCollection actions) + private void UserServiceUserGroupPermissionsAssigned(Core.Events.SaveEventArgs args, IContentService contentService) { var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.EntityId)).ToArray(); - if(entities.Any() == false) + if (entities.Any() == false) { return; } - notifier.Notify(actions.GetAction(), entities); + _notifier.Notify(_actions.GetAction(), entities); } - - private void ContentServiceMoved(Notifier notifier, IContentService sender, Core.Events.MoveEventArgs args, ActionCollection actions) + + private void ContentServiceMoved(Core.Events.MoveEventArgs args) { // notify about the move for all moved items _notifier.Notify(_actions.GetAction(), args.MoveInfoCollection.Select(m => m.Entity).ToArray()); @@ -126,22 +162,22 @@ namespace Umbraco.Web.Compose .Where(m => m.OriginalPath.Contains(Constants.System.RecycleBinContentString)) .Select(m => m.Entity) .ToArray(); - if(restoredEntities.Any()) + if (restoredEntities.Any()) { _notifier.Notify(_actions.GetAction(), restoredEntities); } } - private void PublicAccessServiceSaved(Notifier notifier, IPublicAccessService sender, Core.Events.SaveEventArgs args, IContentService contentService, ActionCollection actions) + private void PublicAccessServiceSaved(Core.Events.SaveEventArgs args, IContentService contentService) { var entities = contentService.GetByIds(args.SavedEntities.Select(e => e.ProtectedNodeId)).ToArray(); - if(entities.Any() == false) + if (entities.Any() == false) { return; } - notifier.Notify(actions.GetAction(), entities); + _notifier.Notify(_actions.GetAction(), entities); } - + /// /// This class is used to send the notifications /// diff --git a/src/Umbraco.Web/Compose/PublicAccessComponent.cs b/src/Umbraco.Web/Compose/PublicAccessComponent.cs index ef7532253b..dcf57ee984 100644 --- a/src/Umbraco.Web/Compose/PublicAccessComponent.cs +++ b/src/Umbraco.Web/Compose/PublicAccessComponent.cs @@ -14,7 +14,9 @@ namespace Umbraco.Web.Compose } public void Terminate() - { } + { + MemberGroupService.Saved -= MemberGroupService_Saved; + } static void MemberGroupService_Saved(IMemberGroupService sender, Core.Events.SaveEventArgs e) { diff --git a/src/Umbraco.Web/Logging/WebProfilerComponent.cs b/src/Umbraco.Web/Logging/WebProfilerComponent.cs index 2959e12ad7..1cb2142199 100755 --- a/src/Umbraco.Web/Logging/WebProfilerComponent.cs +++ b/src/Umbraco.Web/Logging/WebProfilerComponent.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Web; using Umbraco.Core.Composing; using Umbraco.Core.Logging; @@ -9,6 +10,7 @@ namespace Umbraco.Web.Logging { private readonly WebProfiler _profiler; private readonly bool _profile; + private readonly List _terminate = new List(); public WebProfilerComponent(IProfiler profiler, ILogger logger) { @@ -35,15 +37,23 @@ namespace Umbraco.Web.Logging } public void Terminate() - { } + { + UmbracoApplicationBase.ApplicationInit -= InitializeApplication; + foreach (var t in _terminate) t(); + } private void InitializeApplication(object sender, EventArgs args) { if (!(sender is HttpApplication app)) return; // for *each* application (this will run more than once) - app.BeginRequest += (s, a) => _profiler.UmbracoApplicationBeginRequest(s, a); - app.EndRequest += (s, a) => _profiler.UmbracoApplicationEndRequest(s, a); + void beginRequest(object s, EventArgs a) => _profiler.UmbracoApplicationBeginRequest(s, a); + app.BeginRequest += beginRequest; + _terminate.Add(() => app.BeginRequest -= beginRequest); + + void endRequest(object s, EventArgs a) => _profiler.UmbracoApplicationEndRequest(s, a); + app.EndRequest += endRequest; + _terminate.Add(() => app.EndRequest -= endRequest); } } } diff --git a/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs b/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs index df603d9e88..eaa209c055 100644 --- a/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs +++ b/src/Umbraco.Web/PropertyEditors/PropertyEditorsComponent.cs @@ -1,7 +1,11 @@ -using System.Linq; +using System; +using System.Collections.Generic; +using System.Linq; using Umbraco.Core.Composing; +using Umbraco.Core.Events; using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; using Umbraco.Core.Services.Implement; namespace Umbraco.Web.PropertyEditors @@ -9,6 +13,7 @@ namespace Umbraco.Web.PropertyEditors internal sealed class PropertyEditorsComponent : IComponent { private readonly PropertyEditorCollection _propertyEditors; + private readonly List _terminate = new List(); public PropertyEditorsComponent(PropertyEditorCollection propertyEditors) { @@ -27,32 +32,48 @@ namespace Umbraco.Web.PropertyEditors } public void Terminate() - { } - - private static void Initialize(FileUploadPropertyEditor fileUpload) { - MediaService.Saving += fileUpload.MediaServiceSaving; - ContentService.Copied += fileUpload.ContentServiceCopied; - - MediaService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); - MemberService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + foreach (var t in _terminate) t(); } - private static void Initialize(ImageCropperPropertyEditor imageCropper) + private void Initialize(FileUploadPropertyEditor fileUpload) + { + MediaService.Saving += fileUpload.MediaServiceSaving; + _terminate.Add(() => MediaService.Saving -= fileUpload.MediaServiceSaving); + ContentService.Copied += fileUpload.ContentServiceCopied; + _terminate.Add(() => ContentService.Copied -= fileUpload.ContentServiceCopied); + + void mediaServiceDeleted(IMediaService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + MediaService.Deleted += mediaServiceDeleted; + _terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted); + + void contentServiceDeleted(IContentService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.Deleted += contentServiceDeleted; + _terminate.Add(() => ContentService.Deleted -= contentServiceDeleted); + + void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(fileUpload.ServiceDeleted(args.DeletedEntities.Cast())); + MemberService.Deleted += memberServiceDeleted; + _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); + } + + private void Initialize(ImageCropperPropertyEditor imageCropper) { MediaService.Saving += imageCropper.MediaServiceSaving; + _terminate.Add(() => MediaService.Saving -= imageCropper.MediaServiceSaving); ContentService.Copied += imageCropper.ContentServiceCopied; + _terminate.Add(() => ContentService.Copied -= imageCropper.ContentServiceCopied); - MediaService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); - ContentService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); - MemberService.Deleted += (sender, args) - => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + void mediaServiceDeleted(IMediaService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + MediaService.Deleted += mediaServiceDeleted; + _terminate.Add(() => MediaService.Deleted -= mediaServiceDeleted); + + void contentServiceDeleted(IContentService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + ContentService.Deleted += contentServiceDeleted; + _terminate.Add(() => ContentService.Deleted -= contentServiceDeleted); + + void memberServiceDeleted(IMemberService sender, DeleteEventArgs args) => args.MediaFilesToDelete.AddRange(imageCropper.ServiceDeleted(args.DeletedEntities.Cast())); + MemberService.Deleted += memberServiceDeleted; + _terminate.Add(() => MemberService.Deleted -= memberServiceDeleted); } } } diff --git a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs index fcf6f55f44..490fade3c6 100644 --- a/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs +++ b/src/Umbraco.Web/Routing/RedirectTrackingComponent.cs @@ -52,7 +52,12 @@ namespace Umbraco.Web.Routing } public void Terminate() - { } + { + ContentService.Publishing -= ContentService_Publishing; + ContentService.Published -= ContentService_Published; + ContentService.Moving -= ContentService_Moving; + ContentService.Moved -= ContentService_Moved; + } private void ContentService_Publishing(IContentService sender, PublishEventArgs args) { diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 149b4d1436..24be243cea 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -117,7 +117,13 @@ namespace Umbraco.Web.Search } public void Terminate() - { } + { + ContentCacheRefresher.CacheUpdated -= ContentCacheRefresherUpdated; + ContentTypeCacheRefresher.CacheUpdated -= ContentTypeCacheRefresherUpdated; + MediaCacheRefresher.CacheUpdated -= MediaCacheRefresherUpdated; + MemberCacheRefresher.CacheUpdated -= MemberCacheRefresherUpdated; + LanguageCacheRefresher.CacheUpdated -= LanguageCacheRefresherUpdated; + } [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("This method should not be used and will be removed in future versions, rebuilding indexes can be done with the IndexRebuilder or the BackgroundIndexRebuilder")] From b327399cd2c637a715792dc1d40ac1e6253ae3eb Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 22 Sep 2020 08:11:58 +0200 Subject: [PATCH 08/15] adding icon svg data from IconController. --- src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml | 8 ++------ src/Umbraco.Web/Editors/BackOfficeModel.cs | 6 ++++++ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml index d0f0fd2cc8..666f7a64f5 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Default.cshtml @@ -4,7 +4,6 @@ @using Umbraco.Core.Composing @using Umbraco.Core.IO @using Umbraco.Web -@using Umbraco.Core.Configuration @inherits WebViewPage @@ -122,12 +121,9 @@ @Html.AngularValueTinyMceAssets() 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", ''); - iconHelper.defineIcon("icon-delete", ''); - + iconHelper.defineIcon("icon-check", '@Html.Raw(Model.IconCheckData)'); + iconHelper.defineIcon("icon-delete", '@Html.Raw(Model.IconDeleteData)'); }]); //required for the noscript trick diff --git a/src/Umbraco.Web/Editors/BackOfficeModel.cs b/src/Umbraco.Web/Editors/BackOfficeModel.cs index 9833121301..b11ea6a5d1 100644 --- a/src/Umbraco.Web/Editors/BackOfficeModel.cs +++ b/src/Umbraco.Web/Editors/BackOfficeModel.cs @@ -6,13 +6,19 @@ namespace Umbraco.Web.Editors public class BackOfficeModel { + private IconController IconController { get; } public BackOfficeModel(UmbracoFeatures features, IGlobalSettings globalSettings) { Features = features; GlobalSettings = globalSettings; + IconController = new IconController(); + IconCheckData = IconController.GetIcon("icon-check")?.SvgString; + IconDeleteData = IconController.GetIcon("icon-delete")?.SvgString; } public UmbracoFeatures Features { get; } public IGlobalSettings GlobalSettings { get; } + public string IconCheckData { get; } + public string IconDeleteData { get; } } } From 6e56b16342571935c38f06fcb131694265ea295b Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 22 Sep 2020 08:57:48 +0200 Subject: [PATCH 09/15] adding IconService --- src/Umbraco.Web/Composing/Current.cs | 3 + .../Editors/BackOfficeController.cs | 23 +++- src/Umbraco.Web/Editors/BackOfficeModel.cs | 8 +- src/Umbraco.Web/Editors/IconController.cs | 81 ++------------ src/Umbraco.Web/Runtime/WebInitialComposer.cs | 2 +- src/Umbraco.Web/Services/IIconService.cs | 29 +++++ src/Umbraco.Web/Services/IconService.cs | 100 ++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 2 + 8 files changed, 166 insertions(+), 82 deletions(-) create mode 100644 src/Umbraco.Web/Services/IIconService.cs create mode 100644 src/Umbraco.Web/Services/IconService.cs diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 2419eaa6d4..14898d0c02 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -146,6 +146,9 @@ namespace Umbraco.Web.Composing public static ISectionService SectionService => Factory.GetInstance(); + public static IIconService IconService + => Factory.GetInstance(); + #endregion #region Web Constants diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index e77a1b70f2..3e16dd9692 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -15,7 +14,6 @@ using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Cache; using Umbraco.Core.Configuration; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Manifest; using Umbraco.Core.Models.Identity; @@ -26,6 +24,7 @@ using Umbraco.Web.Composing; using Umbraco.Web.Features; using Umbraco.Web.JavaScript; using Umbraco.Web.Security; +using Umbraco.Web.Services; using Constants = Umbraco.Core.Constants; using JArray = Newtonsoft.Json.Linq.JArray; @@ -42,6 +41,7 @@ namespace Umbraco.Web.Editors private readonly ManifestParser _manifestParser; private readonly UmbracoFeatures _features; private readonly IRuntimeState _runtimeState; + private readonly IIconService _iconService; private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; @@ -51,6 +51,16 @@ namespace Umbraco.Web.Editors _manifestParser = manifestParser; _features = features; _runtimeState = runtimeState; + _iconService = Current.IconService; + } + + public BackOfficeController(ManifestParser manifestParser, UmbracoFeatures features, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IIconService iconService) + : base(globalSettings, umbracoContextAccessor, services, appCaches, profilingLogger, umbracoHelper) + { + _manifestParser = manifestParser; + _features = features; + _runtimeState = runtimeState; + _iconService = iconService; } protected BackOfficeSignInManager SignInManager => _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager()); @@ -65,9 +75,14 @@ namespace Umbraco.Web.Editors /// public async Task Default() { + var backofficeModel = new BackOfficeModel(_features, GlobalSettings) + { + IconCheckData = _iconService.GetIcon("icon-check")?.SvgString, + IconDeleteData = _iconService.GetIcon("icon-delete")?.SvgString + }; return await RenderDefaultOrProcessExternalLoginAsync( - () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings)), - () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings))); + () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", backofficeModel), + () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", backofficeModel)); } [HttpGet] diff --git a/src/Umbraco.Web/Editors/BackOfficeModel.cs b/src/Umbraco.Web/Editors/BackOfficeModel.cs index b11ea6a5d1..ba85a64b42 100644 --- a/src/Umbraco.Web/Editors/BackOfficeModel.cs +++ b/src/Umbraco.Web/Editors/BackOfficeModel.cs @@ -6,19 +6,15 @@ namespace Umbraco.Web.Editors public class BackOfficeModel { - private IconController IconController { get; } public BackOfficeModel(UmbracoFeatures features, IGlobalSettings globalSettings) { Features = features; GlobalSettings = globalSettings; - IconController = new IconController(); - IconCheckData = IconController.GetIcon("icon-check")?.SvgString; - IconDeleteData = IconController.GetIcon("icon-delete")?.SvgString; } public UmbracoFeatures Features { get; } public IGlobalSettings GlobalSettings { get; } - public string IconCheckData { get; } - public string IconDeleteData { get; } + public string IconCheckData { get; set; } + public string IconDeleteData { get; set; } } } diff --git a/src/Umbraco.Web/Editors/IconController.cs b/src/Umbraco.Web/Editors/IconController.cs index 72af2194ae..2554f633df 100644 --- a/src/Umbraco.Web/Editors/IconController.cs +++ b/src/Umbraco.Web/Editors/IconController.cs @@ -1,21 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; -using Umbraco.Core.Logging; using Umbraco.Web.Models; -using System.IO; -using Umbraco.Core; -using Umbraco.Core.IO; -using Ganss.XSS; -using Umbraco.Core.Cache; +using Umbraco.Web.Services; namespace Umbraco.Web.Editors { [PluginController("UmbracoApi")] public class IconController : UmbracoAuthorizedApiController { + private readonly IIconService _iconService; + + public IconController(IIconService iconService) + { + _iconService = iconService; + } /// /// Gets an IconModel containing the icon name and SvgString according to an icon name found at the global icons path @@ -24,21 +23,7 @@ namespace Umbraco.Web.Editors /// public IconModel GetIcon(string iconName) { - return string.IsNullOrWhiteSpace(iconName) - ? null - : CreateIconModel(iconName.StripFileExtension(), IOHelper.MapPath($"{GlobalSettings.IconsPath}/{iconName}.svg")); - } - - /// - /// Gets an IconModel using values from a FileInfo model - /// - /// - /// - public IconModel GetIcon(FileInfo fileInfo) - { - return fileInfo == null || string.IsNullOrWhiteSpace(fileInfo.Name) - ? null - : CreateIconModel(fileInfo.Name.StripFileExtension(), fileInfo.FullName); + return _iconService.GetIcon(iconName); } /// @@ -47,53 +32,7 @@ namespace Umbraco.Web.Editors /// public List GetAllIcons() { - var icons = new List(); - var directory = new DirectoryInfo(IOHelper.MapPath($"{GlobalSettings.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; - } - - /// - /// Gets an IconModel containing the icon name and SvgString - /// - /// - /// - /// - 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; - } + return _iconService.GetAllIcons(); } } } diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 135d54560b..112910930e 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -137,8 +137,8 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); composition.RegisterUnique(); composition.RegisterUnique(); - composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(factory => ExamineManager.Instance); diff --git a/src/Umbraco.Web/Services/IIconService.cs b/src/Umbraco.Web/Services/IIconService.cs new file mode 100644 index 0000000000..cdd75f8d4b --- /dev/null +++ b/src/Umbraco.Web/Services/IIconService.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.IO; +using Umbraco.Web.Models; + +namespace Umbraco.Web.Services +{ + public interface IIconService + { + /// + /// Gets an IconModel containing the icon name and SvgString according to an icon name found at the global icons path + /// + /// + /// + IconModel GetIcon(string iconName); + + /// + /// Gets an IconModel using values from a FileInfo model + /// + /// + /// + IconModel GetIcon(FileInfo fileInfo); + + /// + /// Gets a list of all svg icons found at at the global icons path. + /// + /// + List GetAllIcons(); + } +} diff --git a/src/Umbraco.Web/Services/IconService.cs b/src/Umbraco.Web/Services/IconService.cs new file mode 100644 index 0000000000..4093f69174 --- /dev/null +++ b/src/Umbraco.Web/Services/IconService.cs @@ -0,0 +1,100 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Ganss.XSS; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Web.Models; + +namespace Umbraco.Web.Services +{ + internal class IconService : IIconService + { + private readonly IGlobalSettings _globalSettings; + + public IconService(IGlobalSettings globalSettings) + { + _globalSettings = globalSettings; + } + + /// + /// Gets an IconModel containing the icon name and SvgString according to an icon name found at the global icons path + /// + /// + /// + public IconModel GetIcon(string iconName) + { + return string.IsNullOrWhiteSpace(iconName) + ? null + : CreateIconModel(iconName.StripFileExtension(), IOHelper.MapPath($"{_globalSettings.IconsPath}/{iconName}.svg")); + } + + /// + /// Gets an IconModel using values from a FileInfo model + /// + /// + /// + public IconModel GetIcon(FileInfo fileInfo) + { + return fileInfo == null || string.IsNullOrWhiteSpace(fileInfo.Name) + ? null + : CreateIconModel(fileInfo.Name.StripFileExtension(), fileInfo.FullName); + } + + /// + /// Gets a list of all svg icons found at at the global icons path. + /// + /// + public List GetAllIcons() + { + var icons = new List(); + var directory = new DirectoryInfo(IOHelper.MapPath($"{_globalSettings.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; + } + + /// + /// Gets an IconModel containing the icon name and SvgString + /// + /// + /// + /// + 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; + } + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 33051671ee..2efc6fdd56 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -284,10 +284,12 @@ + + From ccc0e82579617f7706dd1561ae6b211bcda97de0 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 22 Sep 2020 08:58:16 +0200 Subject: [PATCH 10/15] #8919 - Avoid having fat controller and do not new up a controller in a model --- src/Umbraco.Web/Composing/Current.cs | 3 + .../Editors/BackOfficeController.cs | 48 +++++++++- src/Umbraco.Web/Editors/BackOfficeModel.cs | 22 +++-- .../Editors/BackOfficePreviewModel.cs | 22 ++++- src/Umbraco.Web/Editors/IconController.cs | 83 +++------------- src/Umbraco.Web/Editors/PreviewController.cs | 23 ++++- src/Umbraco.Web/Runtime/WebInitialComposer.cs | 1 + src/Umbraco.Web/Services/IIconService.cs | 21 +++++ src/Umbraco.Web/Services/IconService.cs | 94 +++++++++++++++++++ src/Umbraco.Web/Umbraco.Web.csproj | 4 +- 10 files changed, 234 insertions(+), 87 deletions(-) create mode 100644 src/Umbraco.Web/Services/IIconService.cs create mode 100644 src/Umbraco.Web/Services/IconService.cs diff --git a/src/Umbraco.Web/Composing/Current.cs b/src/Umbraco.Web/Composing/Current.cs index 2419eaa6d4..14898d0c02 100644 --- a/src/Umbraco.Web/Composing/Current.cs +++ b/src/Umbraco.Web/Composing/Current.cs @@ -146,6 +146,9 @@ namespace Umbraco.Web.Composing public static ISectionService SectionService => Factory.GetInstance(); + public static IIconService IconService + => Factory.GetInstance(); + #endregion #region Web Constants diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index e77a1b70f2..c9106cffb5 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -26,6 +26,7 @@ using Umbraco.Web.Composing; using Umbraco.Web.Features; using Umbraco.Web.JavaScript; using Umbraco.Web.Security; +using Umbraco.Web.Services; using Constants = Umbraco.Core.Constants; using JArray = Newtonsoft.Json.Linq.JArray; @@ -42,15 +43,54 @@ namespace Umbraco.Web.Editors private readonly ManifestParser _manifestParser; private readonly UmbracoFeatures _features; private readonly IRuntimeState _runtimeState; + private readonly IIconService _iconService; private BackOfficeUserManager _userManager; private BackOfficeSignInManager _signInManager; - public BackOfficeController(ManifestParser manifestParser, UmbracoFeatures features, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ServiceContext services, AppCaches appCaches, IProfilingLogger profilingLogger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) + [Obsolete("Use the constructor that injects IIconService.")] + public BackOfficeController( + ManifestParser manifestParser, + UmbracoFeatures features, + IGlobalSettings globalSettings, + IUmbracoContextAccessor umbracoContextAccessor, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IRuntimeState runtimeState, + UmbracoHelper umbracoHelper) + : this(manifestParser, + features, + globalSettings, + umbracoContextAccessor, + services, + appCaches, + profilingLogger, + runtimeState, + umbracoHelper, + Current.IconService) + { + _manifestParser = manifestParser; + _features = features; + _runtimeState = runtimeState; + } + + public BackOfficeController( + ManifestParser manifestParser, + UmbracoFeatures features, + IGlobalSettings globalSettings, + IUmbracoContextAccessor umbracoContextAccessor, + ServiceContext services, + AppCaches appCaches, + IProfilingLogger profilingLogger, + IRuntimeState runtimeState, + UmbracoHelper umbracoHelper, + IIconService iconService) : base(globalSettings, umbracoContextAccessor, services, appCaches, profilingLogger, umbracoHelper) { _manifestParser = manifestParser; _features = features; _runtimeState = runtimeState; + _iconService = iconService; } protected BackOfficeSignInManager SignInManager => _signInManager ?? (_signInManager = OwinContext.GetBackOfficeSignInManager()); @@ -66,8 +106,8 @@ namespace Umbraco.Web.Editors public async Task Default() { return await RenderDefaultOrProcessExternalLoginAsync( - () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings)), - () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings))); + () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _iconService)), + () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/Default.cshtml", new BackOfficeModel(_features, GlobalSettings, _iconService))); } [HttpGet] @@ -150,7 +190,7 @@ namespace Umbraco.Web.Editors { return await RenderDefaultOrProcessExternalLoginAsync( //The default view to render when there is no external login info or errors - () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/AuthorizeUpgrade.cshtml", new BackOfficeModel(_features, GlobalSettings)), + () => View(GlobalSettings.Path.EnsureEndsWith('/') + "Views/AuthorizeUpgrade.cshtml", new BackOfficeModel(_features, GlobalSettings, _iconService)), //The ActionResult to perform if external login is successful () => Redirect("/")); } diff --git a/src/Umbraco.Web/Editors/BackOfficeModel.cs b/src/Umbraco.Web/Editors/BackOfficeModel.cs index b11ea6a5d1..b620d893e3 100644 --- a/src/Umbraco.Web/Editors/BackOfficeModel.cs +++ b/src/Umbraco.Web/Editors/BackOfficeModel.cs @@ -1,21 +1,29 @@ -using Umbraco.Core.Configuration; +using System; +using Umbraco.Core.Configuration; +using Umbraco.Web.Composing; using Umbraco.Web.Features; +using Umbraco.Web.Services; namespace Umbraco.Web.Editors { public class BackOfficeModel { - private IconController IconController { get; } - public BackOfficeModel(UmbracoFeatures features, IGlobalSettings globalSettings) + + + [Obsolete("Use the overload that injects IIconService.")] + public BackOfficeModel(UmbracoFeatures features, IGlobalSettings globalSettings) : this(features, globalSettings, Current.IconService) + { + + } + public BackOfficeModel(UmbracoFeatures features, IGlobalSettings globalSettings, IIconService iconService) { Features = features; GlobalSettings = globalSettings; - IconController = new IconController(); - IconCheckData = IconController.GetIcon("icon-check")?.SvgString; - IconDeleteData = IconController.GetIcon("icon-delete")?.SvgString; + IconCheckData = iconService.GetIcon("icon-check")?.SvgString; + IconDeleteData = iconService.GetIcon("icon-delete")?.SvgString; } - + public UmbracoFeatures Features { get; } public IGlobalSettings GlobalSettings { get; } public string IconCheckData { get; } diff --git a/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs b/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs index b66e432699..7239bba8f7 100644 --- a/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs +++ b/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs @@ -1,7 +1,11 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; + using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Web.Composing; using Umbraco.Web.Features; +using Umbraco.Web.Services; namespace Umbraco.Web.Editors { @@ -10,7 +14,21 @@ namespace Umbraco.Web.Editors private readonly UmbracoFeatures _features; public IEnumerable Languages { get; } - public BackOfficePreviewModel(UmbracoFeatures features, IGlobalSettings globalSettings, IEnumerable languages) : base(features, globalSettings) + [Obsolete("Use the overload that injects IIconService.")] + public BackOfficePreviewModel( + UmbracoFeatures features, + IGlobalSettings globalSettings, + IEnumerable languages) + : this(features, globalSettings, languages, Current.IconService) + { + } + + public BackOfficePreviewModel( + UmbracoFeatures features, + IGlobalSettings globalSettings, + IEnumerable languages, + IIconService iconService) + : base(features, globalSettings, iconService) { _features = features; Languages = languages; diff --git a/src/Umbraco.Web/Editors/IconController.cs b/src/Umbraco.Web/Editors/IconController.cs index 72af2194ae..b45b0948c8 100644 --- a/src/Umbraco.Web/Editors/IconController.cs +++ b/src/Umbraco.Web/Editors/IconController.cs @@ -1,21 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Collections.Generic; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; -using Umbraco.Core.Logging; using Umbraco.Web.Models; -using System.IO; -using Umbraco.Core; -using Umbraco.Core.IO; -using Ganss.XSS; -using Umbraco.Core.Cache; +using Umbraco.Web.Services; namespace Umbraco.Web.Editors { [PluginController("UmbracoApi")] public class IconController : UmbracoAuthorizedApiController { + private readonly IIconService _iconService; + + public IconController(IIconService iconService) + { + _iconService = iconService; + } /// /// Gets an IconModel containing the icon name and SvgString according to an icon name found at the global icons path @@ -24,76 +23,16 @@ namespace Umbraco.Web.Editors /// public IconModel GetIcon(string iconName) { - return string.IsNullOrWhiteSpace(iconName) - ? null - : CreateIconModel(iconName.StripFileExtension(), IOHelper.MapPath($"{GlobalSettings.IconsPath}/{iconName}.svg")); - } - - /// - /// Gets an IconModel using values from a FileInfo model - /// - /// - /// - public IconModel GetIcon(FileInfo fileInfo) - { - return fileInfo == null || string.IsNullOrWhiteSpace(fileInfo.Name) - ? null - : CreateIconModel(fileInfo.Name.StripFileExtension(), fileInfo.FullName); + return _iconService.GetIcon(iconName); } /// /// Gets a list of all svg icons found at at the global icons path. /// /// - public List GetAllIcons() + public IList GetAllIcons() { - var icons = new List(); - var directory = new DirectoryInfo(IOHelper.MapPath($"{GlobalSettings.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; - } - - /// - /// Gets an IconModel containing the icon name and SvgString - /// - /// - /// - /// - 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; - } + return _iconService.GetAllIcons(); } } } diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index c1848419dc..f148655acd 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -12,6 +12,7 @@ using Umbraco.Web.JavaScript; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; +using Umbraco.Web.Services; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors @@ -24,19 +25,39 @@ namespace Umbraco.Web.Editors private readonly IPublishedSnapshotService _publishedSnapshotService; private readonly IUmbracoContextAccessor _umbracoContextAccessor; private readonly ILocalizationService _localizationService; + private readonly IIconService _iconService; + [Obsolete("Use the constructor that injects IIconService.")] public PreviewController( UmbracoFeatures features, IGlobalSettings globalSettings, IPublishedSnapshotService publishedSnapshotService, IUmbracoContextAccessor umbracoContextAccessor, ILocalizationService localizationService) + :this(features, + globalSettings, + publishedSnapshotService, + umbracoContextAccessor, + localizationService, + Current.IconService) + { + + } + + public PreviewController( + UmbracoFeatures features, + IGlobalSettings globalSettings, + IPublishedSnapshotService publishedSnapshotService, + IUmbracoContextAccessor umbracoContextAccessor, + ILocalizationService localizationService, + IIconService iconService) { _features = features; _globalSettings = globalSettings; _publishedSnapshotService = publishedSnapshotService; _umbracoContextAccessor = umbracoContextAccessor; _localizationService = localizationService; + _iconService = iconService; } [UmbracoAuthorize(redirectToUmbracoLogin: true)] @@ -45,7 +66,7 @@ namespace Umbraco.Web.Editors { var availableLanguages = _localizationService.GetAllLanguages(); - var model = new BackOfficePreviewModel(_features, _globalSettings, availableLanguages); + var model = new BackOfficePreviewModel(_features, _globalSettings, availableLanguages, _iconService); if (model.PreviewExtendedHeaderView.IsNullOrWhiteSpace() == false) { diff --git a/src/Umbraco.Web/Runtime/WebInitialComposer.cs b/src/Umbraco.Web/Runtime/WebInitialComposer.cs index 135d54560b..d9a8ee37f2 100644 --- a/src/Umbraco.Web/Runtime/WebInitialComposer.cs +++ b/src/Umbraco.Web/Runtime/WebInitialComposer.cs @@ -139,6 +139,7 @@ namespace Umbraco.Web.Runtime composition.RegisterUnique(); composition.RegisterUnique(); + composition.RegisterUnique(); composition.RegisterUnique(factory => ExamineManager.Instance); diff --git a/src/Umbraco.Web/Services/IIconService.cs b/src/Umbraco.Web/Services/IIconService.cs new file mode 100644 index 0000000000..177921ceae --- /dev/null +++ b/src/Umbraco.Web/Services/IIconService.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Umbraco.Web.Models; + +namespace Umbraco.Web.Services +{ + public interface IIconService + { + /// + /// Gets an IconModel containing the icon name and SvgString according to an icon name found at the global icons path + /// + /// + /// + IconModel GetIcon(string iconName); + + /// + /// Gets a list of all svg icons found at at the global icons path. + /// + /// + IList GetAllIcons(); + } +} diff --git a/src/Umbraco.Web/Services/IconService.cs b/src/Umbraco.Web/Services/IconService.cs new file mode 100644 index 0000000000..93389668b6 --- /dev/null +++ b/src/Umbraco.Web/Services/IconService.cs @@ -0,0 +1,94 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Ganss.XSS; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.IO; +using Umbraco.Web.Models; + +namespace Umbraco.Web.Services +{ + public class IconService : IIconService + { + private readonly IGlobalSettings _globalSettings; + + public IconService(IGlobalSettings globalSettings) + { + _globalSettings = globalSettings; + } + + + /// + public IList GetAllIcons() + { + var icons = new List(); + var directory = new DirectoryInfo(IOHelper.MapPath($"{_globalSettings.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; + } + + /// + public IconModel GetIcon(string iconName) + { + return string.IsNullOrWhiteSpace(iconName) + ? null + : CreateIconModel(iconName.StripFileExtension(), IOHelper.MapPath($"{_globalSettings.IconsPath}/{iconName}.svg")); + } + + /// + /// Gets an IconModel using values from a FileInfo model + /// + /// + /// + private IconModel GetIcon(FileInfo fileInfo) + { + return fileInfo == null || string.IsNullOrWhiteSpace(fileInfo.Name) + ? null + : CreateIconModel(fileInfo.Name.StripFileExtension(), fileInfo.FullName); + } + + /// + /// Gets an IconModel containing the icon name and SvgString + /// + /// + /// + /// + 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; + } + } + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 33051671ee..ff67493aaa 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -238,8 +238,8 @@ - + @@ -284,10 +284,12 @@ + + From 164fa24ef3269c5c6f397fb6d07a3b785c70c970 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 22 Sep 2020 10:04:55 +0200 Subject: [PATCH 11/15] #8919 - Fix issue with wrong casing of variable name --- .../src/common/services/iconhelper.service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js index f714cba4ad..5034de67eb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/iconhelper.service.js @@ -209,7 +209,7 @@ function iconHelper($http, $q, $sce, $timeout, umbRequestHelper) { ,'Failed to retrieve icon: ' + iconName) .then(icon => { if(icon) { - var trustedIcon = this.defineIcon(icon.name, icon.SvgString); + var trustedIcon = this.defineIcon(icon.Name, icon.SvgString); liveRequests = _.filter(liveRequests, iconRequestPath); From 1badb69481ef82cc09990a79c642a0f406f9e384 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 22 Sep 2020 10:14:34 +0200 Subject: [PATCH 12/15] #8919 - Clean up obsolete ctor --- src/Umbraco.Web/Editors/BackOfficeController.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 71bda4bfd2..cd5a3a50b6 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -67,9 +67,7 @@ namespace Umbraco.Web.Editors umbracoHelper, Current.IconService) { - _manifestParser = manifestParser; - _features = features; - _runtimeState = runtimeState; + } public BackOfficeController( From 041d0469b618fa70c0ff07a3141f16a1e8d5b918 Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 22 Sep 2020 11:42:44 +0200 Subject: [PATCH 13/15] Merge remote-tracking branch 'origin/v8/bugfix/examine-reindex' into v8/8.6 (cherry picked from commit 8fddb52f9a76a60fcb212a436417891489b4465f) --- src/Umbraco.Examine/ContentValueSetBuilder.cs | 5 ++ src/Umbraco.Examine/UmbracoContentIndex.cs | 6 +- src/Umbraco.Web/Scheduling/SimpleTask.cs | 32 +++++++ src/Umbraco.Web/Search/ExamineComponent.cs | 84 +++++++++++++------ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 5 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 src/Umbraco.Web/Scheduling/SimpleTask.cs diff --git a/src/Umbraco.Examine/ContentValueSetBuilder.cs b/src/Umbraco.Examine/ContentValueSetBuilder.cs index b8477a9047..dfd64fcd47 100644 --- a/src/Umbraco.Examine/ContentValueSetBuilder.cs +++ b/src/Umbraco.Examine/ContentValueSetBuilder.cs @@ -60,6 +60,11 @@ namespace Umbraco.Examine scope.Complete(); } + return GetValueSetsEnumerable(content, creatorIds, writerIds); + } + + private IEnumerable GetValueSetsEnumerable(IContent[] content, Dictionary creatorIds, Dictionary writerIds) + { // TODO: There is a lot of boxing going on here and ultimately all values will be boxed by Lucene anyways // but I wonder if there's a way to reduce the boxing that we have to do or if it will matter in the end since // Lucene will do it no matter what? One idea was to create a `FieldValue` struct which would contain `object`, `object[]`, `ValueType` and `ValueType[]` diff --git a/src/Umbraco.Examine/UmbracoContentIndex.cs b/src/Umbraco.Examine/UmbracoContentIndex.cs index e266ca789d..67e430b4e9 100644 --- a/src/Umbraco.Examine/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine/UmbracoContentIndex.cs @@ -62,7 +62,7 @@ namespace Umbraco.Examine /// protected override void PerformIndexItems(IEnumerable values, Action onComplete) { - //We don't want to re-enumerate this list, but we need to split it into 2x enumerables: invalid and valid items. + // We don't want to re-enumerate this list, but we need to split it into 2x enumerables: invalid and valid items. // The Invalid items will be deleted, these are items that have invalid paths (i.e. moved to the recycle bin, etc...) // Then we'll index the Value group all together. // We return 0 or 1 here so we can order the results and do the invalid first and then the valid. @@ -80,7 +80,7 @@ namespace Umbraco.Examine || !validator.ValidateProtectedContent(path, v.Category)) ? 0 : 1; - }); + }).ToList(); var hasDeletes = false; var hasUpdates = false; @@ -99,7 +99,7 @@ namespace Umbraco.Examine { hasUpdates = true; //these are the valid ones, so just index them all at once - base.PerformIndexItems(group, onComplete); + base.PerformIndexItems(group.ToList(), onComplete); } } diff --git a/src/Umbraco.Web/Scheduling/SimpleTask.cs b/src/Umbraco.Web/Scheduling/SimpleTask.cs new file mode 100644 index 0000000000..a8603915b0 --- /dev/null +++ b/src/Umbraco.Web/Scheduling/SimpleTask.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Umbraco.Web.Scheduling +{ + /// + /// A simple task that executes a delegate synchronously + /// + internal class SimpleTask : IBackgroundTask + { + private readonly Action _action; + + public SimpleTask(Action action) + { + _action = action; + } + + public bool IsAsync => false; + + public void Run() => _action(); + + public Task RunAsync(CancellationToken token) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + } + } +} diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 24be243cea..ca16aaee65 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -17,9 +17,13 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Examine.LuceneEngine.Directories; using Umbraco.Core.Composing; using System.ComponentModel; +using System.Threading; +using Umbraco.Web.Scheduling; namespace Umbraco.Web.Search { + + public sealed class ExamineComponent : Umbraco.Core.Composing.IComponent { private readonly IExamineManager _examineManager; @@ -34,7 +38,8 @@ namespace Umbraco.Web.Search private readonly IMainDom _mainDom; private readonly IProfilingLogger _logger; private readonly IUmbracoIndexesCreator _indexCreator; - + private readonly BackgroundTaskRunner _indexItemTaskRunner; + // the default enlist priority is 100 // enlist with a lower priority to ensure that anything "default" runs after us @@ -62,6 +67,7 @@ namespace Umbraco.Web.Search _mainDom = mainDom; _logger = profilingLogger; _indexCreator = indexCreator; + _indexItemTaskRunner = new BackgroundTaskRunner(_logger); } public void Initialize() @@ -580,12 +586,18 @@ namespace Umbraco.Web.Search } } + /// + /// An action that will execute at the end of the Scope being completed + /// private abstract class DeferedAction { public virtual void Execute() { } } + /// + /// Re-indexes an item on a background thread + /// private class DeferedReIndexForContent : DeferedAction { private readonly ExamineComponent _examineComponent; @@ -606,21 +618,32 @@ namespace Umbraco.Web.Search public static void Execute(ExamineComponent examineComponent, IContent content, bool isPublished) { - foreach (var index in examineComponent._examineManager.Indexes.OfType() - //filter the indexers - .Where(x => isPublished || !x.PublishedValuesOnly) - .Where(x => x.EnableDefaultEventHandler)) + // perform the ValueSet lookup on a background thread + examineComponent._indexItemTaskRunner.Add(new SimpleTask(() => { - //for content we have a different builder for published vs unpublished - var builder = index.PublishedValuesOnly - ? examineComponent._publishedContentValueSetBuilder - : (IValueSetBuilder)examineComponent._contentValueSetBuilder; + // for content we have a different builder for published vs unpublished + // we don't want to build more value sets than is needed so we'll lazily build 2 one for published one for non-published + var builders = new Dictionary>> + { + [true] = new Lazy>(() => examineComponent._publishedContentValueSetBuilder.GetValueSets(content).ToList()), + [false] = new Lazy>(() => examineComponent._contentValueSetBuilder.GetValueSets(content).ToList()) + }; - index.IndexItems(builder.GetValueSets(content)); - } + foreach (var index in examineComponent._examineManager.Indexes.OfType() + //filter the indexers + .Where(x => isPublished || !x.PublishedValuesOnly) + .Where(x => x.EnableDefaultEventHandler)) + { + var valueSet = builders[index.PublishedValuesOnly].Value; + index.IndexItems(valueSet); + } + })); } } + /// + /// Re-indexes an item on a background thread + /// private class DeferedReIndexForMedia : DeferedAction { private readonly ExamineComponent _examineComponent; @@ -641,18 +664,25 @@ namespace Umbraco.Web.Search public static void Execute(ExamineComponent examineComponent, IMedia media, bool isPublished) { - var valueSet = examineComponent._mediaValueSetBuilder.GetValueSets(media).ToList(); - - foreach (var index in examineComponent._examineManager.Indexes.OfType() - //filter the indexers - .Where(x => isPublished || !x.PublishedValuesOnly) - .Where(x => x.EnableDefaultEventHandler)) + // perform the ValueSet lookup on a background thread + examineComponent._indexItemTaskRunner.Add(new SimpleTask(() => { - index.IndexItems(valueSet); - } + var valueSet = examineComponent._mediaValueSetBuilder.GetValueSets(media).ToList(); + + foreach (var index in examineComponent._examineManager.Indexes.OfType() + //filter the indexers + .Where(x => isPublished || !x.PublishedValuesOnly) + .Where(x => x.EnableDefaultEventHandler)) + { + index.IndexItems(valueSet); + } + })); } } + /// + /// Re-indexes an item on a background thread + /// private class DeferedReIndexForMember : DeferedAction { private readonly ExamineComponent _examineComponent; @@ -671,13 +701,17 @@ namespace Umbraco.Web.Search public static void Execute(ExamineComponent examineComponent, IMember member) { - var valueSet = examineComponent._memberValueSetBuilder.GetValueSets(member).ToList(); - foreach (var index in examineComponent._examineManager.Indexes.OfType() - //filter the indexers - .Where(x => x.EnableDefaultEventHandler)) + // perform the ValueSet lookup on a background thread + examineComponent._indexItemTaskRunner.Add(new SimpleTask(() => { - index.IndexItems(valueSet); - } + var valueSet = examineComponent._memberValueSetBuilder.GetValueSets(member).ToList(); + foreach (var index in examineComponent._examineManager.Indexes.OfType() + //filter the indexers + .Where(x => x.EnableDefaultEventHandler)) + { + index.IndexItems(valueSet); + } + })); } } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 5e1176cdc6..e75330140e 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -271,6 +271,7 @@ + From a2cb264f270eb790d43f56105480ed1381edab3e Mon Sep 17 00:00:00 2001 From: Claus Date: Tue, 22 Sep 2020 13:14:03 +0200 Subject: [PATCH 14/15] Merge remote-tracking branch 'origin/v8/bugfix/8433-member-login-sql-locks' into v8/8.6 (cherry picked from commit 6122bffa24a6ee5e554c285ee01629552abc0118) --- .../Repositories/IMemberRepository.cs | 17 ++- .../Implement/MemberRepository.cs | 107 +++++++++++++++--- .../Implement/SimpleGetRepository.cs | 2 + .../Repositories/Implement/UserRepository.cs | 8 +- .../Services/IMembershipMemberService.cs | 15 ++- .../Services/Implement/MemberService.cs | 33 +++--- .../Services/Implement/UserService.cs | 7 ++ .../Repositories/UserRepositoryTest.cs | 61 +++++++--- .../Services/MemberServiceTests.cs | 54 +++++++++ .../Cache/DistributedCacheExtensions.cs | 7 +- src/Umbraco.Web/Cache/MemberCacheRefresher.cs | 96 ++++++++++++---- src/Umbraco.Web/Security/MembershipHelper.cs | 37 ++---- .../Providers/UmbracoMembershipProvider.cs | 36 ++++-- 13 files changed, 363 insertions(+), 117 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs index 245c024356..c737c2bf66 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IMemberRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; @@ -6,6 +7,8 @@ namespace Umbraco.Core.Persistence.Repositories { public interface IMemberRepository : IContentRepository { + IMember GetByUsername(string username); + /// /// Finds members in a given role /// @@ -35,5 +38,17 @@ namespace Umbraco.Core.Persistence.Repositories /// /// int GetCountByQuery(IQuery query); + + /// + /// Sets a members last login date based on their username + /// + /// + /// + /// + /// This is a specialized method because whenever a member logs in, the membership provider requires us to set the 'online' which requires + /// updating their login date. This operation must be fast and cannot use database locks which is fine if we are only executing a single query + /// for this data since there won't be any other data contention issues. + /// + void SetLastLogin(string username, DateTime date); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 42e7d1c32f..687e35aef3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -25,6 +25,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private readonly IMemberTypeRepository _memberTypeRepository; private readonly ITagRepository _tagRepository; private readonly IMemberGroupRepository _memberGroupRepository; + private readonly IRepositoryCachePolicy _memberByUsernameCachePolicy; public MemberRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, IMemberTypeRepository memberTypeRepository, IMemberGroupRepository memberGroupRepository, ITagRepository tagRepository, ILanguageRepository languageRepository, IRelationRepository relationRepository, IRelationTypeRepository relationTypeRepository, @@ -34,6 +35,8 @@ namespace Umbraco.Core.Persistence.Repositories.Implement _memberTypeRepository = memberTypeRepository ?? throw new ArgumentNullException(nameof(memberTypeRepository)); _tagRepository = tagRepository ?? throw new ArgumentNullException(nameof(tagRepository)); _memberGroupRepository = memberGroupRepository; + + _memberByUsernameCachePolicy = new DefaultRepositoryCachePolicy(GlobalIsolatedCache, ScopeAccessor, DefaultOptions); } protected override MemberRepository This => this; @@ -382,12 +385,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (changedCols.Count > 0) Database.Update(dto, changedCols); - // replace the property data - var deletePropertyDataSql = SqlContext.Sql().Delete().Where(x => x.VersionId == member.VersionId); - Database.Execute(deletePropertyDataSql); + // Replace the property data + // Lookup the data to update with a UPDLOCK (using ForUpdate()) this is because we have another method that doesn't take an explicit WriteLock + // in SetLastLogin which is called very often and we want to avoid the lock timeout for the explicit lock table but we still need to ensure atomic + // operations between that method and this one. + + var propDataSql = SqlContext.Sql().Select("*").From().Where(x => x.VersionId == member.VersionId).ForUpdate(); + var existingPropData = Database.Fetch(propDataSql).ToDictionary(x => x.PropertyTypeId); var propertyDataDtos = PropertyFactory.BuildDtos(member.ContentType.Variations, member.VersionId, 0, entity.Properties, LanguageRepository, out _, out _); foreach (var propertyDataDto in propertyDataDtos) - Database.Insert(propertyDataDto); + { + // Check if this already exists and update, else insert a new one + if (existingPropData.TryGetValue(propertyDataDto.PropertyTypeId, out var propData)) + { + propertyDataDto.Id = propData.Id; + Database.Update(propertyDataDto); + } + else + { + Database.Insert(propertyDataDto); + } + } SetEntityTags(entity, _tagRepository); @@ -505,6 +523,56 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return Database.ExecuteScalar(fullSql); } + /// + public void SetLastLogin(string username, DateTime date) + { + // Important - these queries are designed to execute without an exclusive WriteLock taken in our distributed lock + // table. However due to the data that we are updating which relies on version data we cannot update this data + // without taking some locks, otherwise we'll end up with strange situations because when a member is updated, that operation + // deletes and re-inserts all property data. So if there are concurrent transactions, one deleting and re-inserting and another trying + // to update there can be problems. This is only an issue for cmsPropertyData, not umbracoContentVersion because that table just + // maintains a single row and it isn't deleted/re-inserted. + // So the important part here is the ForUpdate() call on the select to fetch the property data to update. + + // Update the cms property value for the member + + var sqlSelectTemplateProperty = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin1", s => s + .Select(x => x.Id) + .From() + .InnerJoin().On((l, r) => l.Id == r.PropertyTypeId) + .InnerJoin().On((l, r) => l.Id == r.VersionId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) + .Where(x => x.Alias == SqlTemplate.Arg("propertyTypeAlias")) + .Where(x => x.LoginName == SqlTemplate.Arg("username")) + .ForUpdate()); + var sqlSelectProperty = sqlSelectTemplateProperty.Sql(Constants.ObjectTypes.Member, Constants.Conventions.Member.LastLoginDate, username); + + var update = Sql() + .Update(u => u + .Set(x => x.DateValue, date)) + .WhereIn(x => x.Id, sqlSelectProperty); + + Database.Execute(update); + + // Update the umbracoContentVersion value for the member + + var sqlSelectTemplateVersion = SqlContext.Templates.Get("Umbraco.Core.MemberRepository.SetLastLogin2", s => s + .Select(x => x.Id) + .From() + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .InnerJoin().On((l, r) => l.NodeId == r.NodeId) + .Where(x => x.NodeObjectType == SqlTemplate.Arg("nodeObjectType")) + .Where(x => x.LoginName == SqlTemplate.Arg("username"))); + var sqlSelectVersion = sqlSelectTemplateVersion.Sql(Constants.ObjectTypes.Member, username); + + Database.Execute(Sql() + .Update(u => u + .Set(x => x.VersionDate, date)) + .WhereIn(x => x.Id, sqlSelectVersion)); + } + /// /// Gets paged member results. /// @@ -528,20 +596,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement ordering); } - private string _pagedResultsByQueryWhere; - - private string GetPagedResultsByQueryWhere() - { - if (_pagedResultsByQueryWhere == null) - _pagedResultsByQueryWhere = " AND (" - + $"({SqlSyntax.GetQuotedTableName("umbracoNode")}.{SqlSyntax.GetQuotedColumnName("text")} LIKE @0)" - + " OR " - + $"({SqlSyntax.GetQuotedTableName("cmsMember")}.{SqlSyntax.GetQuotedColumnName("LoginName")} LIKE @0)" - + ")"; - - return _pagedResultsByQueryWhere; - } - protected override string ApplySystemOrdering(ref Sql sql, Ordering ordering) { if (ordering.OrderBy.InvariantEquals("email")) @@ -631,5 +685,22 @@ namespace Umbraco.Core.Persistence.Repositories.Implement member.ResetDirtyProperties(false); return member; } + + public IMember GetByUsername(string username) + { + return _memberByUsernameCachePolicy.Get(username, PerformGetByUsername, PerformGetAllByUsername); + } + + private IMember PerformGetByUsername(string username) + { + var query = Query().Where(x => x.Username.Equals(username)); + return PerformGetByQuery(query).FirstOrDefault(); + } + + private IEnumerable PerformGetAllByUsername(params string[] usernames) + { + var query = Query().WhereIn(x => x.Username, usernames); + return PerformGetByQuery(query); + } } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs index f7e59820c3..d327fba67f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/SimpleGetRepository.cs @@ -11,6 +11,8 @@ using Umbraco.Core.Scoping; namespace Umbraco.Core.Persistence.Repositories.Implement { + // TODO: Obsolete this, change all implementations of this like in Dictionary to just use custom Cache policies like in the member repository. + /// /// Simple abstract ReadOnly repository used to simply have PerformGet and PeformGetAll with an underlying cache /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 3be5102b83..4721037674 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -168,10 +168,7 @@ ORDER BY colName"; } public Guid CreateLoginSession(int userId, string requestingIpAddress, bool cleanStaleSessions = true) - { - // TODO: I know this doesn't follow the normal repository conventions which would require us to create a UserSessionRepository - //and also business logic models for these objects but that's just so overkill for what we are doing - //and now that everything is properly in a transaction (Scope) there doesn't seem to be much reason for using that anymore + { var now = DateTime.UtcNow; var dto = new UserLoginDto { @@ -201,13 +198,14 @@ ORDER BY colName"; // that query is going to run a *lot*, make it a template var t = SqlContext.Templates.Get("Umbraco.Core.UserRepository.ValidateLoginSession", s => s .Select() + .SelectTop(1) .From() .Where(x => x.SessionId == SqlTemplate.Arg("sessionId")) .ForUpdate()); var sql = t.Sql(sessionId); - var found = Database.Query(sql).FirstOrDefault(); + var found = Database.FirstOrDefault(sql); if (found == null || found.UserId != userId || found.LoggedOutUtc.HasValue) return false; diff --git a/src/Umbraco.Core/Services/IMembershipMemberService.cs b/src/Umbraco.Core/Services/IMembershipMemberService.cs index 448b0c761a..3c6b4f6672 100644 --- a/src/Umbraco.Core/Services/IMembershipMemberService.cs +++ b/src/Umbraco.Core/Services/IMembershipMemberService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence.Querying; @@ -107,6 +108,18 @@ namespace Umbraco.Core.Services /// or to Delete void Delete(T membershipUser); + /// + /// Sets the last login date for the member if they are found by username + /// + /// + /// + /// + /// This is a specialized method because whenever a member logs in, the membership provider requires us to set the 'online' which requires + /// updating their login date. This operation must be fast and cannot use database locks which is fine if we are only executing a single query + /// for this data since there won't be any other data contention issues. + /// + void SetLastLogin(string username, DateTime date); + /// /// Saves an /// diff --git a/src/Umbraco.Core/Services/Implement/MemberService.cs b/src/Umbraco.Core/Services/Implement/MemberService.cs index 9b97c6b161..877bb5c9ce 100644 --- a/src/Umbraco.Core/Services/Implement/MemberService.cs +++ b/src/Umbraco.Core/Services/Implement/MemberService.cs @@ -447,15 +447,10 @@ namespace Umbraco.Core.Services.Implement /// public IMember GetByUsername(string username) { - // TODO: Somewhere in here, whether at this level or the repository level, we need to add - // a caching mechanism since this method is used by all the membership providers and could be - // called quite a bit when dealing with members. - using (var scope = ScopeProvider.CreateScope(autoComplete: true)) { - scope.ReadLock(Constants.Locks.MemberTree); - var query = Query().Where(x => x.Username.Equals(username)); - return _memberRepository.Get(query).FirstOrDefault(); + scope.ReadLock(Constants.Locks.MemberTree); + return _memberRepository.GetByUsername(username); } } @@ -806,12 +801,17 @@ namespace Umbraco.Core.Services.Implement #region Save - /// - /// Saves an - /// - /// to Save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events + /// + public void SetLastLogin(string username, DateTime date) + { + using (var scope = ScopeProvider.CreateScope()) + { + _memberRepository.SetLastLogin(username, date); + scope.Complete(); + } + } + + /// public void Save(IMember member, bool raiseEvents = true) { //trimming username and email to make sure we have no trailing space @@ -847,12 +847,7 @@ namespace Umbraco.Core.Services.Implement } } - /// - /// Saves a list of objects - /// - /// to save - /// Optional parameter to raise events. - /// Default is True otherwise set to False to not raise events + /// public void Save(IEnumerable members, bool raiseEvents = true) { var membersA = members.ToArray(); diff --git a/src/Umbraco.Core/Services/Implement/UserService.cs b/src/Umbraco.Core/Services/Implement/UserService.cs index 6f5cbfca56..bf732263a9 100644 --- a/src/Umbraco.Core/Services/Implement/UserService.cs +++ b/src/Umbraco.Core/Services/Implement/UserService.cs @@ -254,6 +254,13 @@ namespace Umbraco.Core.Services.Implement } } + // explicit implementation because we don't need it now but due to the way that the members membership provider is put together + // this method must exist in this service as an implementation (legacy) + void IMembershipMemberService.SetLastLogin(string username, DateTime date) + { + throw new NotSupportedException("This method is not implemented or supported for users"); + } + /// /// Saves an /// diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index bbefb79f6b..b2efbd34b8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -16,6 +16,7 @@ using Umbraco.Tests.Testing; using Umbraco.Core.Persistence; using Umbraco.Core.PropertyEditors; using System; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Tests.Persistence.Repositories { @@ -76,12 +77,44 @@ namespace Umbraco.Tests.Persistence.Repositories return new UserGroupRepository(accessor, AppCaches.Disabled, Logger); } + [Test] + public void Validate_Login_Session() + { + // Arrange + var provider = TestObjects.GetScopeProvider(Logger); + var user = MockedUser.CreateUser(); + using (var scope = provider.CreateScope(autoComplete: true)) + { + var repository = CreateRepository(provider); + repository.Save(user); + } + + using (var scope = provider.CreateScope(autoComplete: true)) + { + var repository = CreateRepository(provider); + var sessionId = repository.CreateLoginSession(user.Id, "1.2.3.4"); + + // manually update this record to be in the past + scope.Database.Execute(SqlContext.Sql() + .Update(u => u.Set(x => x.LoggedOutUtc, DateTime.UtcNow.AddDays(-100))) + .Where(x => x.SessionId == sessionId)); + + var isValid = repository.ValidateLoginSession(user.Id, sessionId); + Assert.IsFalse(isValid); + + // create a new one + sessionId = repository.CreateLoginSession(user.Id, "1.2.3.4"); + isValid = repository.ValidateLoginSession(user.Id, sessionId); + Assert.IsTrue(isValid); + } + } + [Test] public void Can_Perform_Add_On_UserRepository() { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -101,7 +134,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -125,7 +158,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -150,7 +183,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var userRepository = CreateRepository(provider); var contentRepository = CreateContentRepository(provider, out var contentTypeRepo); @@ -209,7 +242,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -237,7 +270,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); var userGroupRepository = CreateUserGroupRepository(provider); @@ -260,7 +293,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -280,7 +313,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -301,7 +334,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -322,7 +355,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -341,7 +374,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -360,7 +393,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Paged_Results_By_Query_And_Filter_And_Groups() { var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -393,7 +426,7 @@ namespace Umbraco.Tests.Persistence.Repositories public void Can_Get_Paged_Results_With_Filter_And_Groups() { var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); @@ -426,7 +459,7 @@ namespace Umbraco.Tests.Persistence.Repositories { // Arrange var provider = TestObjects.GetScopeProvider(Logger); - using (var scope = provider.CreateScope()) + using (var scope = provider.CreateScope(autoComplete: true)) { var repository = CreateRepository(provider); var userGroupRepository = CreateUserGroupRepository(provider); diff --git a/src/Umbraco.Tests/Services/MemberServiceTests.cs b/src/Umbraco.Tests/Services/MemberServiceTests.cs index 1385bbe0b3..816bc6d7dd 100644 --- a/src/Umbraco.Tests/Services/MemberServiceTests.cs +++ b/src/Umbraco.Tests/Services/MemberServiceTests.cs @@ -48,6 +48,60 @@ namespace Umbraco.Tests.Services ((MemberService)ServiceContext.MemberService).MembershipProvider = provider; } + [Test] + public void Can_Update_Member_Property_Value() + { + IMemberType memberType = MockedContentTypes.CreateSimpleMemberType(); + ServiceContext.MemberTypeService.Save(memberType); + IMember member = MockedMember.CreateSimpleMember(memberType, "hello", "helloworld@test123.com", "hello", "hello"); + member.SetValue("title", "title of mine"); + ServiceContext.MemberService.Save(member); + + // re-get + member = ServiceContext.MemberService.GetById(member.Id); + member.SetValue("title", "another title of mine"); + ServiceContext.MemberService.Save(member); + + // re-get + member = ServiceContext.MemberService.GetById(member.Id); + Assert.AreEqual("another title of mine", member.GetValue("title")); + } + + [Test] + public void Can_Get_By_Username() + { + var memberType = ServiceContext.MemberTypeService.Get("member"); + IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true); + ServiceContext.MemberService.Save(member); + + var member2 = ServiceContext.MemberService.GetByUsername(member.Username); + + Assert.IsNotNull(member2); + Assert.AreEqual(member.Email, member2.Email); + } + + [Test] + public void Can_Set_Last_Login_Date() + { + var now = DateTime.Now; + var memberType = ServiceContext.MemberTypeService.Get("member"); + IMember member = new Member("xname", "xemail", "xusername", "xrawpassword", memberType, true) + { + LastLoginDate = now, + UpdateDate = now + }; + ServiceContext.MemberService.Save(member); + + var newDate = now.AddDays(10); + ServiceContext.MemberService.SetLastLogin(member.Username, newDate); + + //re-get + member = ServiceContext.MemberService.GetById(member.Id); + + Assert.That(member.LastLoginDate, Is.EqualTo(newDate).Within(1).Seconds); + Assert.That(member.UpdateDate, Is.EqualTo(newDate).Within(1).Seconds); + } + [Test] public void Can_Create_Member_With_Properties() { diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index b00a4818f6..f360d37d03 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -128,15 +128,16 @@ namespace Umbraco.Web.Cache public static void RefreshMemberCache(this DistributedCache dc, params IMember[] members) { - dc.Refresh(MemberCacheRefresher.UniqueId, x => x.Id, members); + if (members.Length == 0) return; + dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username))); } public static void RemoveMemberCache(this DistributedCache dc, params IMember[] members) { - dc.Remove(MemberCacheRefresher.UniqueId, x => x.Id, members); + if (members.Length == 0) return; + dc.RefreshByPayload(MemberCacheRefresher.UniqueId, members.Select(x => new MemberCacheRefresher.JsonPayload(x.Id, x.Username))); } - #endregion #region MemberGroupCache diff --git a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs index 1565b1c849..736a858af3 100644 --- a/src/Umbraco.Web/Cache/MemberCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MemberCacheRefresher.cs @@ -1,4 +1,6 @@ -using System; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; using Umbraco.Core.Cache; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Repositories; @@ -7,14 +9,30 @@ using Umbraco.Core.Services; namespace Umbraco.Web.Cache { - public sealed class MemberCacheRefresher : TypedCacheRefresherBase + public sealed class MemberCacheRefresher : PayloadCacheRefresherBase { private readonly IdkMap _idkMap; + private readonly LegacyMemberCacheRefresher _legacyMemberRefresher; public MemberCacheRefresher(AppCaches appCaches, IdkMap idkMap) : base(appCaches) { _idkMap = idkMap; + _legacyMemberRefresher = new LegacyMemberCacheRefresher(this, appCaches); + } + + public class JsonPayload + { + [JsonConstructor] + public JsonPayload(int id, string username) + { + Id = id; + Username = username; + } + + public int Id { get; } + public string Username { get; } + } #region Define @@ -31,38 +49,45 @@ namespace Umbraco.Web.Cache #region Refresher + public override void Refresh(JsonPayload[] payloads) + { + ClearCache(payloads); + base.Refresh(payloads); + } + public override void Refresh(int id) { - ClearCache(id); + ClearCache(new JsonPayload(id, null)); base.Refresh(id); } public override void Remove(int id) { - ClearCache(id); + ClearCache(new JsonPayload(id, null)); base.Remove(id); } - public override void Refresh(IMember instance) - { - ClearCache(instance.Id); - base.Refresh(instance); - } + [Obsolete("This is no longer used and will be removed from the codebase in the future")] + public void Refresh(IMember instance) => _legacyMemberRefresher.Refresh(instance); - public override void Remove(IMember instance) - { - ClearCache(instance.Id); - base.Remove(instance); - } + [Obsolete("This is no longer used and will be removed from the codebase in the future")] + public void Remove(IMember instance) => _legacyMemberRefresher.Remove(instance); - private void ClearCache(int id) + private void ClearCache(params JsonPayload[] payloads) { - _idkMap.ClearCache(id); AppCaches.ClearPartialViewCache(); - var memberCache = AppCaches.IsolatedCaches.Get(); - if (memberCache) - memberCache.Result.Clear(RepositoryCacheKeys.GetKey(id)); + + foreach (var p in payloads) + { + _idkMap.ClearCache(p.Id); + if (memberCache) + { + memberCache.Result.Clear(RepositoryCacheKeys.GetKey(p.Id)); + memberCache.Result.Clear(RepositoryCacheKeys.GetKey(p.Username)); + } + } + } #endregion @@ -75,5 +100,38 @@ namespace Umbraco.Web.Cache } #endregion + + #region Backwards Compat + + // TODO: this is here purely for backwards compat but should be removed in netcore + private class LegacyMemberCacheRefresher : TypedCacheRefresherBase + { + private readonly MemberCacheRefresher _parent; + + public LegacyMemberCacheRefresher(MemberCacheRefresher parent, AppCaches appCaches) : base(appCaches) + { + _parent = parent; + } + + public override Guid RefresherUniqueId => _parent.RefresherUniqueId; + + public override string Name => _parent.Name; + + protected override MemberCacheRefresher This => _parent; + + public override void Refresh(IMember instance) + { + _parent.ClearCache(new JsonPayload(instance.Id, instance.Username)); + base.Refresh(instance.Id); + } + + public override void Remove(IMember instance) + { + _parent.ClearCache(new JsonPayload(instance.Id, instance.Username)); + base.Remove(instance); + } + } + + #endregion } } diff --git a/src/Umbraco.Web/Security/MembershipHelper.cs b/src/Umbraco.Web/Security/MembershipHelper.cs index f74897d565..aa0623e9e6 100644 --- a/src/Umbraco.Web/Security/MembershipHelper.cs +++ b/src/Umbraco.Web/Security/MembershipHelper.cs @@ -261,8 +261,9 @@ namespace Umbraco.Web.Security { return false; } - //Set member online - var member = provider.GetUser(username, true); + // Get the member, do not set to online - this is done implicitly as part of ValidateUser which is consistent with + // how the .NET framework SqlMembershipProvider works. Passing in true will just cause more unnecessary SQL queries/locks. + var member = provider.GetUser(username, false); if (member == null) { //this should not happen @@ -778,33 +779,17 @@ namespace Umbraco.Web.Security /// private IMember GetCurrentPersistedMember() { - return _appCaches.RequestCache.GetCacheItem( - GetCacheKey("GetCurrentPersistedMember"), () => - { - var provider = _membershipProvider; + var provider = _membershipProvider; - if (provider.IsUmbracoMembershipProvider() == false) - { - throw new NotSupportedException("An IMember model can only be retrieved when using the built-in Umbraco membership providers"); - } - var username = provider.GetCurrentUserName(); - var member = _memberService.GetByUsername(username); - return member; - }); - } - - private static string GetCacheKey(string key, params object[] additional) - { - var sb = new StringBuilder(); - sb.Append(typeof (MembershipHelper).Name); - sb.Append("-"); - sb.Append(key); - foreach (var s in additional) + if (provider.IsUmbracoMembershipProvider() == false) { - sb.Append("-"); - sb.Append(s); + throw new NotSupportedException("An IMember model can only be retrieved when using the built-in Umbraco membership providers"); } - return sb.ToString(); + var username = provider.GetCurrentUserName(); + + // The result of this is cached by the MemberRepository + var member = _memberService.GetByUsername(username); + return member; } } diff --git a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs index 1f7e2c8084..bf9ee654c4 100644 --- a/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs +++ b/src/Umbraco.Web/Security/Providers/UmbracoMembershipProvider.cs @@ -348,15 +348,16 @@ namespace Umbraco.Web.Security.Providers if (userIsOnline) { - member.LastLoginDate = DateTime.Now; - member.UpdateDate = DateTime.Now; - //don't raise events for this! It just sets the member dates, if we do raise events this will - // cause all distributed cache to execute - which will clear out some caches we don't want. - // http://issues.umbraco.org/issue/U4-3451 - // when upgrading from 7.2 to 7.3 trying to save will throw if (UmbracoVersion.Current >= new Version(7, 3, 0, 0)) - MemberService.Save(member, false); + { + var now = DateTime.Now; + // update the database data directly instead of a full member save which requires DB locks + MemberService.SetLastLogin(username, now); + member.LastLoginDate = now; + member.UpdateDate = now; + } + } return ConvertToMembershipUser(member); @@ -555,6 +556,8 @@ namespace Umbraco.Web.Security.Providers var authenticated = CheckPassword(password, member.RawPasswordValue); + var requiresFullSave = false; + if (authenticated == false) { // TODO: Increment login attempts - lock if too many. @@ -574,6 +577,8 @@ namespace Umbraco.Web.Security.Providers { Current.Logger.Info("Login attempt failed for username {Username} from IP address {IpAddress}", username, GetCurrentRequestIpAddress()); } + + requiresFullSave = true; } else { @@ -581,6 +586,7 @@ namespace Umbraco.Web.Security.Providers { //we have successfully logged in, reset the AccessFailedCount member.FailedPasswordAttempts = 0; + requiresFullSave = true; } member.LastLoginDate = DateTime.Now; @@ -588,15 +594,23 @@ namespace Umbraco.Web.Security.Providers Current.Logger.Info("Login attempt succeeded for username {Username} from IP address {IpAddress}", username, GetCurrentRequestIpAddress()); } - //don't raise events for this! It just sets the member dates, if we do raise events this will + // don't raise events for this! It just sets the member dates, if we do raise events this will // cause all distributed cache to execute - which will clear out some caches we don't want. // http://issues.umbraco.org/issue/U4-3451 // TODO: In v8 we aren't going to have an overload to disable events, so we'll need to make a different method // for this type of thing (i.e. UpdateLastLogin or similar). - // when upgrading from 7.2 to 7.3 trying to save will throw - if (UmbracoVersion.Current >= new Version(7, 3, 0, 0)) - MemberService.Save(member, false); + if (requiresFullSave) + { + // when upgrading from 7.2 to 7.3 trying to save will throw + if (UmbracoVersion.Current >= new Version(7, 3, 0, 0)) + MemberService.Save(member, false); + } + else + { + // set the last login date without full save (fast, no locks) + MemberService.SetLastLogin(member.Username, member.LastLoginDate); + } return new ValidateUserResult { From fe3f60412208473fc94c899917036028788262c0 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 23 Sep 2020 16:20:18 +0200 Subject: [PATCH 15/15] Move the IIconService and IconModel to the Umbraco.Core project See issue #8919 (cherry picked from commit 0531fce9e2ca7abaf6bf588df97ce4f570941c67) --- src/{Umbraco.Web => Umbraco.Core}/Models/IconModel.cs | 2 +- .../Services/IIconService.cs | 4 ++-- src/Umbraco.Core/Umbraco.Core.csproj | 2 ++ src/Umbraco.Web/Editors/BackOfficeModel.cs | 2 +- src/Umbraco.Web/Editors/BackOfficePreviewModel.cs | 3 +-- src/Umbraco.Web/Editors/IconController.cs | 4 ++-- src/Umbraco.Web/Services/IconService.cs | 11 ++++++----- src/Umbraco.Web/Umbraco.Web.csproj | 2 -- 8 files changed, 15 insertions(+), 15 deletions(-) rename src/{Umbraco.Web => Umbraco.Core}/Models/IconModel.cs (79%) rename src/{Umbraco.Web => Umbraco.Core}/Services/IIconService.cs (90%) diff --git a/src/Umbraco.Web/Models/IconModel.cs b/src/Umbraco.Core/Models/IconModel.cs similarity index 79% rename from src/Umbraco.Web/Models/IconModel.cs rename to src/Umbraco.Core/Models/IconModel.cs index 5c79ad6219..0de44301bb 100644 --- a/src/Umbraco.Web/Models/IconModel.cs +++ b/src/Umbraco.Core/Models/IconModel.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Web.Models +namespace Umbraco.Core.Models { public class IconModel { diff --git a/src/Umbraco.Web/Services/IIconService.cs b/src/Umbraco.Core/Services/IIconService.cs similarity index 90% rename from src/Umbraco.Web/Services/IIconService.cs rename to src/Umbraco.Core/Services/IIconService.cs index 177921ceae..963edb22a5 100644 --- a/src/Umbraco.Web/Services/IIconService.cs +++ b/src/Umbraco.Core/Services/IIconService.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using Umbraco.Web.Models; +using Umbraco.Core.Models; -namespace Umbraco.Web.Services +namespace Umbraco.Core.Services { public interface IIconService { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f80784f12a..b7b9867618 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -143,12 +143,14 @@ + + diff --git a/src/Umbraco.Web/Editors/BackOfficeModel.cs b/src/Umbraco.Web/Editors/BackOfficeModel.cs index b620d893e3..cbdafd2e94 100644 --- a/src/Umbraco.Web/Editors/BackOfficeModel.cs +++ b/src/Umbraco.Web/Editors/BackOfficeModel.cs @@ -1,8 +1,8 @@ using System; using Umbraco.Core.Configuration; +using Umbraco.Core.Services; using Umbraco.Web.Composing; using Umbraco.Web.Features; -using Umbraco.Web.Services; namespace Umbraco.Web.Editors { diff --git a/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs b/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs index 7239bba8f7..cc7356b687 100644 --- a/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs +++ b/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; - using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Web.Composing; using Umbraco.Web.Features; -using Umbraco.Web.Services; namespace Umbraco.Web.Editors { diff --git a/src/Umbraco.Web/Editors/IconController.cs b/src/Umbraco.Web/Editors/IconController.cs index b45b0948c8..87303a4e62 100644 --- a/src/Umbraco.Web/Editors/IconController.cs +++ b/src/Umbraco.Web/Editors/IconController.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; +using Umbraco.Core.Models; +using Umbraco.Core.Services; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; -using Umbraco.Web.Models; -using Umbraco.Web.Services; namespace Umbraco.Web.Editors { diff --git a/src/Umbraco.Web/Services/IconService.cs b/src/Umbraco.Web/Services/IconService.cs index 93389668b6..206af296f9 100644 --- a/src/Umbraco.Web/Services/IconService.cs +++ b/src/Umbraco.Web/Services/IconService.cs @@ -5,7 +5,8 @@ using Ganss.XSS; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.IO; -using Umbraco.Web.Models; +using Umbraco.Core.Models; +using Umbraco.Core.Services; namespace Umbraco.Web.Services { @@ -68,13 +69,13 @@ namespace Umbraco.Web.Services 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); + sanitizer.AllowedAttributes.UnionWith(Constants.SvgSanitizer.Attributes); + sanitizer.AllowedCssProperties.UnionWith(Constants.SvgSanitizer.Attributes); + sanitizer.AllowedTags.UnionWith(Constants.SvgSanitizer.Tags); try { - var svgContent = File.ReadAllText(iconPath); + var svgContent = System.IO.File.ReadAllText(iconPath); var sanitizedString = sanitizer.Sanitize(svgContent); var svg = new IconModel diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 3187f2b371..c3eba87d6f 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -239,7 +239,6 @@ - @@ -290,7 +289,6 @@ -