From 0abe685d7f9d5c37315ba830fe00fb1b4ee0ccf2 Mon Sep 17 00:00:00 2001 From: elitsa Date: Mon, 3 Dec 2018 09:00:44 +0100 Subject: [PATCH 01/26] Removing malicious code from the name of a Stylesheet. --- src/Umbraco.Core/StringExtensions.cs | 2 +- src/Umbraco.Web/UI/LegacyDialogHandler.cs | 2 +- src/Umbraco.Web/WebServices/SaveFileController.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index 17b6d5a962..a0479e62f2 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -541,7 +541,7 @@ namespace Umbraco.Core /// Returns the string without any html tags. public static string StripHtml(this string text) { - string pattern = "[*{}\\/:<>?|\"-()\\n]"; + string pattern = "[*{}\\/:<>?|\"-+()\\n]"; return Regex.Replace(text, pattern, String.Empty); } diff --git a/src/Umbraco.Web/UI/LegacyDialogHandler.cs b/src/Umbraco.Web/UI/LegacyDialogHandler.cs index efcea4bbd5..a3dc6750e9 100644 --- a/src/Umbraco.Web/UI/LegacyDialogHandler.cs +++ b/src/Umbraco.Web/UI/LegacyDialogHandler.cs @@ -207,7 +207,7 @@ namespace Umbraco.Web.UI typeInstance.TypeID = typeId; typeInstance.ParentID = nodeId; - typeInstance.Alias = text; + typeInstance.Alias = text.CleanForXss(); // check for returning url ITaskReturnUrl returnUrlTask = typeInstance as LegacyDialogTask; diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 5f2fcaeb34..359ee6fc31 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -243,7 +243,7 @@ namespace Umbraco.Web.WebServices // sanitize input - stylesheet names have no extension var svce = (FileService)Services.FileService; - filename = CleanFilename(filename); + filename = CleanFilename(filename.CleanForXss()); oldName = CleanFilename(oldName); if (filename != oldName) From 978f409f234e5ee3eb9a3ed6acb69b0fab44bf04 Mon Sep 17 00:00:00 2001 From: elitsa Date: Mon, 3 Dec 2018 12:07:41 +0100 Subject: [PATCH 02/26] Removing malicious code when creating or editing the name of a xslt file. --- .../umbraco.presentation/umbraco/create/XsltTasks.cs | 2 +- .../umbraco.presentation/umbraco/create/xslt.ascx.cs | 2 +- .../umbraco/developer/Xslt/editXslt.aspx.cs | 2 +- .../umbraco/webservices/codeEditorSave.asmx.cs | 4 +++- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs index 6c6174c0bb..0a6bd540fe 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs @@ -28,7 +28,7 @@ namespace umbraco IOHelper.EnsureFileExists(Path.Combine(IOHelper.MapPath(SystemDirectories.Xslt), "web.config"), Files.BlockingWebConfig); var template = Alias.Substring(0, Alias.IndexOf("|||")); - var fileName = Alias.Substring(Alias.IndexOf("|||") + 3, Alias.Length - Alias.IndexOf("|||") - 3).Replace(" ", ""); + var fileName = Alias.Substring(Alias.IndexOf("|||") + 3, Alias.Length - Alias.IndexOf("|||") - 3); if (fileName.ToLowerInvariant().EndsWith(".xslt") == false) fileName += ".xslt"; var xsltTemplateSource = IOHelper.MapPath(SystemDirectories.Umbraco + "/xslt/templates/" + template); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/xslt.ascx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/xslt.ascx.cs index 16db1a160f..d60859662b 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/xslt.ascx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/xslt.ascx.cs @@ -66,7 +66,7 @@ namespace umbraco.presentation.create BasePage.Current.getUser(), helper.Request("nodeType"), createMacroVal, - xsltName + "|||" + rename.Text); + xsltName + "|||" + rename.Text.CleanForXss()); BasePage.Current.ClientTools .ChangeContentFrameUrl(returnUrl) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs index fe94628ebe..67eb3c5244 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs @@ -97,7 +97,7 @@ namespace umbraco.cms.presentation.developer // Add source and filename - var file = IOHelper.MapPath(SystemDirectories.Xslt + "/" + Request.QueryString["file"]); + var file = IOHelper.MapPath(SystemDirectories.Xslt + "/" + Request.QueryString["file"].CleanForXss().Replace(" ", "")); // validate file IOHelper.ValidateEditPath(file, SystemDirectories.Xslt); diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs index 954cd3860b..862d249af5 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs @@ -61,6 +61,8 @@ namespace umbraco.presentation.webservices [WebMethod] public string SaveXslt(string fileName, string oldName, string fileContents, bool ignoreDebugging) { + fileName = fileName.CleanForXss(); + if (AuthorizeRequest(DefaultApps.developer.ToString())) { IOHelper.EnsurePathExists(SystemDirectories.Xslt); @@ -448,4 +450,4 @@ namespace umbraco.presentation.webservices } } -} \ No newline at end of file +} From ae8e20546fbf86ebb5d4d75cbf6f6dd0a722fa61 Mon Sep 17 00:00:00 2001 From: elitsa Date: Mon, 3 Dec 2018 12:15:01 +0100 Subject: [PATCH 03/26] Reverting - unrelated changes. --- src/Umbraco.Core/StringExtensions.cs | 2 +- src/Umbraco.Web/UI/LegacyDialogHandler.cs | 2 +- src/Umbraco.Web/WebServices/SaveFileController.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/StringExtensions.cs b/src/Umbraco.Core/StringExtensions.cs index a0479e62f2..d83c4e3bc8 100644 --- a/src/Umbraco.Core/StringExtensions.cs +++ b/src/Umbraco.Core/StringExtensions.cs @@ -541,7 +541,7 @@ namespace Umbraco.Core /// Returns the string without any html tags. public static string StripHtml(this string text) { - string pattern = "[*{}\\/:<>?|\"-+()\\n]"; + const string pattern = @"<(.|\n)*?>"; return Regex.Replace(text, pattern, String.Empty); } diff --git a/src/Umbraco.Web/UI/LegacyDialogHandler.cs b/src/Umbraco.Web/UI/LegacyDialogHandler.cs index a3dc6750e9..efcea4bbd5 100644 --- a/src/Umbraco.Web/UI/LegacyDialogHandler.cs +++ b/src/Umbraco.Web/UI/LegacyDialogHandler.cs @@ -207,7 +207,7 @@ namespace Umbraco.Web.UI typeInstance.TypeID = typeId; typeInstance.ParentID = nodeId; - typeInstance.Alias = text.CleanForXss(); + typeInstance.Alias = text; // check for returning url ITaskReturnUrl returnUrlTask = typeInstance as LegacyDialogTask; diff --git a/src/Umbraco.Web/WebServices/SaveFileController.cs b/src/Umbraco.Web/WebServices/SaveFileController.cs index 359ee6fc31..5f2fcaeb34 100644 --- a/src/Umbraco.Web/WebServices/SaveFileController.cs +++ b/src/Umbraco.Web/WebServices/SaveFileController.cs @@ -243,7 +243,7 @@ namespace Umbraco.Web.WebServices // sanitize input - stylesheet names have no extension var svce = (FileService)Services.FileService; - filename = CleanFilename(filename.CleanForXss()); + filename = CleanFilename(filename); oldName = CleanFilename(oldName); if (filename != oldName) From 7fc69e5eb0f3abd30d1629cfef83b6429e52d304 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 29 Nov 2018 20:38:56 +0100 Subject: [PATCH 04/26] Add instructions on how to apply a display name in notification emails --- src/Umbraco.Web.UI/config/umbracoSettings.Release.config | 1 + src/Umbraco.Web.UI/config/umbracoSettings.config | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config index af5b302ff0..2d663aa1fb 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.Release.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.Release.config @@ -32,6 +32,7 @@ + your@email.here diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config index 638d3ef758..d481632a56 100644 --- a/src/Umbraco.Web.UI/config/umbracoSettings.config +++ b/src/Umbraco.Web.UI/config/umbracoSettings.config @@ -48,6 +48,7 @@ + your@email.here From fb094f46befe99433cebed345c931734de6d1778 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 7 Dec 2018 14:17:34 +0100 Subject: [PATCH 05/26] Fix edit content localization errors (#3840) * Fix wrongly formatted localizations * Swap hardcoded time format for a localized one * Split date and time in publish messages --- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 4 ++-- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 ++-- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 4 ++-- src/Umbraco.Web/Editors/ContentController.cs | 6 ++---- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 01e4a2a031..815cc7bce3 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1079,10 +1079,10 @@ Mange hilsner fra Umbraco robotten Ordbogsnøgle gemt Indhold publiceret og nu synligt for besøgende - og nu synligt for besøgende indtil {0} + og nu synligt for besøgende indtil %0% kl. %1% Indhold gemt Husk at publicere for at gøre det synligt for besøgende - Ændringerne bliver publiceret den {0} + Ændringerne bliver publiceret den %0% kl. %1% Send til Godkendelse Rettelser er blevet sendt til godkendelse Medie gemt diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index e22943fd40..319743302b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1397,10 +1397,10 @@ To manage your website, simply open the Umbraco back office and start adding con Publishing failed because the parent page isn't published Content published and visible on the website - and visible on the website until {0} + and visible on the website until %0% at %1% Content saved Remember to publish to make changes visible - Changes will be published on {0} + Changes will be published on %0% at %1% Sent For Approval Changes have been sent for approval Media saved diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 2fd9e6a772..e2142135f9 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1396,10 +1396,10 @@ To manage your website, simply open the Umbraco back office and start adding con Publishing failed because the parent page isn't published Content published and visible on the website - and visible on the website until {0} + and visible on the website until %0% at %1% Content saved Remember to publish to make changes visible - Changes will be published on {0} + Changes will be published on %0% at %1% Sent For Approval Changes have been sent for approval Media saved diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 89bd8016d3..ecc4fe0ff2 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -697,8 +697,7 @@ namespace Umbraco.Web.Editors display.AddSuccessNotification( Services.TextService.Localize("speechBubbles/editContentSavedHeader"), contentItem.ReleaseDate.HasValue - ? string.Format(Services.TextService.Localize("speechBubbles/editContentSavedWithReleaseDateText"), - $"{contentItem.ReleaseDate.Value.ToLongDateString()} {contentItem.ReleaseDate.Value.ToString("HH:mm")}") + ? Services.TextService.Localize("speechBubbles/editContentSavedWithReleaseDateText", new [] { contentItem.ReleaseDate.Value.ToLongDateString(), contentItem.ReleaseDate.Value.ToShortTimeString() }) : Services.TextService.Localize("speechBubbles/editContentSavedText") ); } @@ -1053,8 +1052,7 @@ namespace Umbraco.Web.Editors display.AddSuccessNotification( Services.TextService.Localize("speechBubbles/editContentPublishedHeader"), expireDate.HasValue - ? string.Format(Services.TextService.Localize("speechBubbles/editContentPublishedWithExpireDateText"), - $"{expireDate.Value.ToLongDateString()} {expireDate.Value.ToString("HH:mm")}") + ? Services.TextService.Localize("speechBubbles/editContentPublishedWithExpireDateText", new [] { expireDate.Value.ToLongDateString(), expireDate.Value.ToShortTimeString() }) : Services.TextService.Localize("speechBubbles/editContentPublishedText") ); break; From 1d9bb4605bba814d8954b204f1dd5ea099680031 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Sougnez?= Date: Sun, 9 Dec 2018 17:44:11 +0100 Subject: [PATCH 06/26] Improvement to the .editorconfig file (#3795) --- .editorconfig | 3 ++- .gitignore | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index dbf903ae14..522efe1cf3 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,4 +13,5 @@ indent_size = 4 # Trim trailing whitespace, limited support. # https://github.com/editorconfig/editorconfig/wiki/Property-research:-Trim-trailing-spaces -trim_trailing_whitespace = true \ No newline at end of file +trim_trailing_whitespace = true +csharp_prefer_braces = false : none diff --git a/.gitignore b/.gitignore index 1226db8c71..8950b70c87 100644 --- a/.gitignore +++ b/.gitignore @@ -90,6 +90,7 @@ src/Umbraco.Web.UI.Client/[Bb]uild/* src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/ src/Umbraco.Web.UI/[Uu]ser[Cc]ontrols/ src/Umbraco.Web.UI.Client/src/[Ll]ess/*.css +src/Umbraco.Web.UI.Client/vwd.webinfo tools/NDepend/ src/Umbraco.Web.UI/App_Plugins/* From 3ab5e166d3dba6cf3827c243ffbb467a35bb4d38 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 30 Nov 2018 22:05:17 +0100 Subject: [PATCH 07/26] Update the select avatar step for invited users --- .../src/views/common/dialogs/login.html | 8 +------- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- 4 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html index 3b48bcc9f1..d6b1f0b3bc 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html @@ -88,13 +88,7 @@
- -
diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 815cc7bce3..92a38692a2 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1564,7 +1564,7 @@ Mange hilsner fra Umbraco robotten er blevet inviteret En invitation er blevet sendt til den nye bruger med oplysninger om, hvordan man logger ind i Umbraco. Hej og velkommen til Umbraco! På bare 1 minut vil du være klar til at komme i gang, vi skal bare have dig til at oprette en adgangskode og tilføje et billede til din avatar. - Upload et billede for at gøre det nemt for andre brugere at genkende dig. + Hvis du uploader et billede af dig selv, gør du det nemt for andre brugere at genkende dig. Klik på cirklen ovenfor for at uploade et billede. Forfatter Oversætter Skift diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 319743302b..5a2a28f549 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -1942,7 +1942,7 @@ To manage your website, simply open the Umbraco back office and start adding con An invitation has been sent to the new user with details about how to log in to Umbraco. Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar. Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it. - Upload a picture to make it easy for other users to recognize you. + Uploading a photo of yourself will make it easy for other users to recognize you. Click the circle above to upload your photo. Writer Translator Change diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index e2142135f9..fc5ae4428f 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -1936,7 +1936,7 @@ To manage your website, simply open the Umbraco back office and start adding con An invitation has been sent to the new user with details about how to log in to Umbraco. Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar. Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it. - Upload a picture to make it easy for other users to recognize you. + Uploading a photo of yourself will make it easy for other users to recognize you. Click the circle above to upload your photo. Writer Translator Change From 7149ebcb68036630072ab803b381aa5827b26b73 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 16 Nov 2018 09:34:00 +0100 Subject: [PATCH 08/26] Localize document type scaffolds for nested content (#3483) --- src/Umbraco.Web/Editors/ContentController.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index ecc4fe0ff2..918f639bf1 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -347,6 +347,9 @@ namespace Umbraco.Web.Editors var emptyContent = Services.ContentService.CreateContent("", parentId, contentType.Alias, UmbracoUser.Id); var mapped = AutoMapperExtensions.MapWithUmbracoContext(emptyContent, UmbracoContext); + // translate the content type name if applicable + mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName); + mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name); //remove this tab if it exists: umbContainerView var containerTab = mapped.Tabs.FirstOrDefault(x => x.Alias == Constants.Conventions.PropertyGroups.ListViewGroupName); From 87478163edf051d07f0bcaa38469a6cbf353df09 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 9 Dec 2018 18:19:48 +0100 Subject: [PATCH 09/26] Reload the recycle bin when deleting an item (#3820) --- .../src/views/content/content.delete.controller.js | 6 +++++- .../src/views/media/media.delete.controller.js | 4 ++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js index b0b7fc2312..56010840c3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js @@ -30,9 +30,13 @@ function ContentDeleteController($scope, contentResource, treeService, navigatio var recycleBin = treeService.getDescendantNode(rootNode, -20); if (recycleBin) { recycleBin.hasChildren = true; + //reload the recycle bin if it's already expanded so the deleted item is shown + if (recycleBin.expanded) { + treeService.loadNodeChildren({ node: recycleBin, section: "content" }); + } } } - + //if the current edited item is the same one as we're deleting, we need to navigate elsewhere if (editorState.current && editorState.current.id == $scope.currentNode.id) { diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js index a8be3d0be5..87fcef79d7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js @@ -30,6 +30,10 @@ function MediaDeleteController($scope, mediaResource, treeService, navigationSer var recycleBin = treeService.getDescendantNode(rootNode, -21); if (recycleBin) { recycleBin.hasChildren = true; + //reload the recycle bin if it's already expanded so the deleted item is shown + if (recycleBin.expanded) { + treeService.loadNodeChildren({ node: recycleBin, section: "media" }); + } } } From 7c8ab96b5151452fa9206ca070c349d5b4a8024b Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Fri, 30 Nov 2018 19:13:23 +0100 Subject: [PATCH 10/26] As Gitter has been removed from Our, we might as well remove it here --- .github/CONTRIBUTING.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 96014f65b7..6caeadd0e5 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -82,7 +82,6 @@ You can get in touch with [the PR team](#the-pr-team) in multiple ways, we love - If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward - Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum, the team monitors that one closely -- We're also [active in the Gitter chatroom](https://gitter.im/umbraco/Umbraco-CMS) ## Code of Conduct From 9f9286ae45513bf79b0ce8fced13ae0141fb531e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 8 Dec 2018 10:43:26 +0100 Subject: [PATCH 11/26] Redirect to recycle bin after deletion --- .../src/views/content/content.delete.controller.js | 4 +++- .../src/views/media/media.delete.controller.js | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js index 56010840c3..917e4c024a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js @@ -42,7 +42,9 @@ function ContentDeleteController($scope, contentResource, treeService, navigatio //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent var location = "/content"; - if ($scope.currentNode.parentId.toString() !== "-1") + if ($scope.currentNode.parentId.toString() === "-20") + location = "/content/content/recyclebin"; + else if ($scope.currentNode.parentId.toString() !== "-1") location = "/content/content/edit/" + $scope.currentNode.parentId; $location.path(location); diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js index 87fcef79d7..6ff35ab9cb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.delete.controller.js @@ -41,8 +41,10 @@ function MediaDeleteController($scope, mediaResource, treeService, navigationSer if (editorState.current && editorState.current.id == $scope.currentNode.id) { //If the deleted item lived at the root then just redirect back to the root, otherwise redirect to the item's parent - var location = "/media"; - if ($scope.currentNode.parentId.toString() !== "-1") + var location = "/media"; + if ($scope.currentNode.parentId.toString() === "-21") + location = "/media/media/recyclebin"; + else if ($scope.currentNode.parentId.toString() !== "-1") location = "/media/media/edit/" + $scope.currentNode.parentId; $location.path(location); From 3c74ce2427167c823e61b8211ccd2487bce31929 Mon Sep 17 00:00:00 2001 From: elitsa Date: Mon, 10 Dec 2018 08:55:54 +0100 Subject: [PATCH 12/26] Implementing anti forgery token which will not allows members to be created by sending a request directly to the registration controller when the request is not coming from a page in the application --- .../Controllers/UmbLoginController.cs | 1 + .../Controllers/UmbLoginStatusController.cs | 1 + .../Controllers/UmbProfileController.cs | 1 + .../Controllers/UmbRegisterController.cs | 1 + src/Umbraco.Web/HtmlHelperRenderExtensions.cs | 19 ++++++++++++++++--- 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index df67be72ce..ba46d0a17e 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -11,6 +11,7 @@ namespace Umbraco.Web.Controllers public class UmbLoginController : SurfaceController { [HttpPost] + [ValidateAntiForgeryToken] public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs index 9bb8ae7c9a..8e063bf2a3 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginStatusController.cs @@ -12,6 +12,7 @@ namespace Umbraco.Web.Controllers public class UmbLoginStatusController : SurfaceController { [HttpPost] + [ValidateAntiForgeryToken] public ActionResult HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/Controllers/UmbProfileController.cs b/src/Umbraco.Web/Controllers/UmbProfileController.cs index b45723ed30..7def7af826 100644 --- a/src/Umbraco.Web/Controllers/UmbProfileController.cs +++ b/src/Umbraco.Web/Controllers/UmbProfileController.cs @@ -15,6 +15,7 @@ namespace Umbraco.Web.Controllers public class UmbProfileController : SurfaceController { [HttpPost] + [ValidateAntiForgeryToken] public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model) { var provider = global::Umbraco.Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider(); diff --git a/src/Umbraco.Web/Controllers/UmbRegisterController.cs b/src/Umbraco.Web/Controllers/UmbRegisterController.cs index 823d243eec..7931565c47 100644 --- a/src/Umbraco.Web/Controllers/UmbRegisterController.cs +++ b/src/Umbraco.Web/Controllers/UmbRegisterController.cs @@ -10,6 +10,7 @@ namespace Umbraco.Web.Controllers public class UmbRegisterController : SurfaceController { [HttpPost] + [ValidateAntiForgeryToken] public ActionResult HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model) { if (ModelState.IsValid == false) diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 3062613b6b..30b4e64e33 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Linq; using System.Text; using System.Web; +using System.Web.Helpers; using System.Web.Mvc; using System.Web.Mvc.Html; using System.Web.Routing; @@ -289,6 +290,7 @@ namespace Umbraco.Web { _viewContext = viewContext; _method = method; + _controllerName = controllerName; _encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals); } @@ -296,13 +298,24 @@ namespace Umbraco.Web private readonly FormMethod _method; private bool _disposed; private readonly string _encryptedString; + private readonly string _controllerName; - protected override void Dispose(bool disposing) + protected override void Dispose(bool disposing) { if (this._disposed) return; this._disposed = true; + //Detect if the call is targeting UmbRegisterController/UmbProfileController/UmbLoginStatusController/UmbLoginController and if it is we automatically output a AntiForgeryToken() + // We have a controllerName and area so we can match + if (_controllerName == "UmbRegister" + || _controllerName == "UmbProfile" + || _controllerName == "UmbLoginStatus" + || _controllerName == "UmbLogin") + { + _viewContext.Writer.Write(AntiForgery.GetHtml().ToString()); + } + //write out the hidden surface form routes _viewContext.Writer.Write(""); @@ -813,8 +826,8 @@ namespace Umbraco.Web } htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); - //new UmbracoForm: - var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, method, additionalRouteVals); + //new UmbracoForm: + var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, method, additionalRouteVals); if (traditionalJavascriptEnabled) { From 150ab1366863e1fd7456346c67c9c8dd4d1c433c Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Thu, 15 Nov 2018 20:13:52 +0000 Subject: [PATCH 13/26] Honour 'Order Direction' passed into GetPagedResultsByQuery Wanting to update the MiniListView so that you can change the sort order of the results, so that the most recent items are first, eg sort by descending, however chasing this through to the Entity Repository GetPagedResultsByQuery although the sort direction is passed at each stage, inside this method it is ignored... is this the best way to add it back in? --- .../Persistence/Repositories/EntityRepository.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index adfce25cce..f28b226932 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -64,7 +64,8 @@ namespace Umbraco.Core.Persistence.Repositories }, objectTypeId); var translator = new SqlTranslator(sqlClause, query); var entitySql = translator.Translate(); - var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)).OrderBy("umbracoNode.id"); + var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)); + pagedSql = (orderDirection == Direction.Descending) ? pagedSql.OrderByDescending("umbracoNode.id") : pagedSql.OrderBy("umbracoNode.id"); IEnumerable result; @@ -80,8 +81,8 @@ namespace Umbraco.Core.Persistence.Repositories foreach (var idGroup in ids) { var propSql = GetPropertySql(Constants.ObjectTypes.Media) - .Where("contentNodeId IN (@ids)", new {ids = idGroup}) - .OrderBy("contentNodeId"); + .Where("contentNodeId IN (@ids)", new { ids = idGroup }); + propSql = (orderDirection == Direction.Descending) ? propSql.OrderByDescending("contentNodeId") : propSql.OrderBy("contentNodeId"); //This does NOT fetch all data into memory in a list, this will read // over the records as a data reader, this is much better for performance and memory, From 08dc5a40381a6a1e6b27bb22844a196ba294a08b Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 3 Dec 2018 13:20:50 +0100 Subject: [PATCH 14/26] Fix the disappearing action button by using a promise --- .../src/common/services/navigation.service.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 5b0706150c..46c25adf1e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -25,6 +25,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo var mainTreeEventHandler = null; //tracks the user profile dialog var userDialog = null; + var syncTreePromise; function setMode(mode) { switch (mode) { @@ -176,6 +177,11 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo //when a tree is loaded into a section, we need to put it into appState mainTreeEventHandler.bind("treeLoaded", function(ev, args) { appState.setTreeState("currentRootNode", args.tree); + if (syncTreePromise) { + mainTreeEventHandler.syncTree(syncTreePromise.args).then(function(syncArgs) { + syncTreePromise.resolve(syncArgs); + }); + } }); //when a tree node is synced this event will fire, this allows us to set the currentNode @@ -297,8 +303,10 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo } } - //couldn't sync - return angularHelper.rejectedPromise(); + //create a promise and resolve it later + syncTreePromise = $q.defer(); + syncTreePromise.args = args; + return syncTreePromise.promise; }, /** From 96f529c6698f7430e3e324972d550cad792d7bb8 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 16 Dec 2018 17:18:09 +0100 Subject: [PATCH 15/26] Fix "allow all row configurations" for grid layouts --- .../grid/dialogs/layoutconfig.controller.js | 20 +++++++++---------- .../grid/dialogs/layoutconfig.html | 3 +-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js index e2422e7da0..566535fc98 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js @@ -24,16 +24,6 @@ angular.module("umbraco") return ((spans / $scope.columns) * 100).toFixed(8); }; - $scope.toggleCollection = function(collection, toggle){ - if(toggle){ - collection = []; - }else{ - delete collection; - } - }; - - - /**************** Section *****************/ @@ -47,8 +37,18 @@ angular.module("umbraco") } $scope.currentSection = section; + $scope.currentSection.allowAll = section.allowAll || !section.allowed || !section.allowed.length; }; + $scope.toggleAllowed = function (section) { + if (section.allowed) { + delete section.allowed; + } + else { + section.allowed = []; + } + } + $scope.deleteSection = function(section, template) { if ($scope.currentSection === section) { $scope.currentSection = undefined; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html index f87262daaf..0e1a92a62c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.html @@ -61,8 +61,7 @@ + ng-change="toggleAllowed(currentSection)" /> From 410ab3447ce9a0d8a0a37ada0c7a27f819eaab4a Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 16 Dec 2018 17:16:31 +0100 Subject: [PATCH 16/26] Fix "toggle all editors" for grid rows --- .../grid/dialogs/rowconfig.controller.js | 20 +++++++++---------- .../grid/dialogs/rowconfig.html | 3 +-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js index 3cd16301cd..4e3dde50e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js @@ -22,16 +22,6 @@ function RowConfigController($scope) { return ((spans / $scope.columns) * 100).toFixed(8); }; - $scope.toggleCollection = function(collection, toggle) { - if (toggle) { - collection = []; - } - else { - delete collection; - } - }; - - /**************** area *****************/ @@ -55,9 +45,19 @@ function RowConfigController($scope) { row.areas.push(cell); } $scope.currentCell = cell; + $scope.currentCell.allowAll = cell.allowAll || !cell.allowed || !cell.allowed.length; } }; + $scope.toggleAllowed = function (cell) { + if (cell.allowed) { + delete cell.allowed; + } + else { + cell.allowed = []; + } + } + $scope.deleteArea = function (cell, row) { if ($scope.currentCell === cell) { $scope.currentCell = undefined; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html index 542ef7da86..8ae229d048 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html @@ -72,8 +72,7 @@ From f3e8b70b8cf816a8030da0913f1af05500ddf86a Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 30 Nov 2018 15:07:48 +0100 Subject: [PATCH 17/26] Show only the success message upon successful update of notifications --- src/Umbraco.Web.UI.Client/src/views/content/notify.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/notify.html b/src/Umbraco.Web.UI.Client/src/views/content/notify.html index cb12d92f08..e7c4d4d785 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/notify.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/notify.html @@ -15,10 +15,11 @@
{{currentNode.name}}
+ -
+
-
Set your notification for {{ currentNode.name }}
+ Set your notification for {{ currentNode.name }}
- -
    -
  • +
    +
      +
    • + + {{group.containerPath}} +
    • +
    • -
      - -
      +
      + +
      - - -
    • -
    + +
  • +
+
diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index 1d728c63a5..5c75bc07f7 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -109,6 +109,25 @@ namespace Umbraco.Web.Editors var availableCompositions = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes); + Func> getEntityContainers = contentType => + { + if (contentType == null) + { + return null; + } + switch (type) + { + case UmbracoObjectTypes.DocumentType: + return Services.ContentTypeService.GetContentTypeContainers(contentType as IContentType); + case UmbracoObjectTypes.MediaType: + return Services.ContentTypeService.GetMediaTypeContainers(contentType as IMediaType); + case UmbracoObjectTypes.MemberType: + return new EntityContainer[0]; + default: + throw new ArgumentOutOfRangeException("The entity type was not a content type"); + } + }; + var currCompositions = source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray(); var compAliases = currCompositions.Select(x => x.Alias).ToArray(); var ancestors = availableCompositions.Ancestors.Select(x => x.Alias); @@ -117,9 +136,6 @@ namespace Umbraco.Web.Editors .Select(x => new Tuple(Mapper.Map(x.Composition), x.Allowed)) .Select(x => { - //translate the name - x.Item1.Name = TranslateItem(x.Item1.Name); - //we need to ensure that the item is enabled if it is already selected // but do not allow it if it is any of the ancestors if (compAliases.Contains(x.Item1.Alias) && ancestors.Contains(x.Item1.Alias) == false) @@ -128,6 +144,14 @@ namespace Umbraco.Web.Editors x = new Tuple(x.Item1, true); } + //translate the name + x.Item1.Name = TranslateItem(x.Item1.Name); + + var contentType = allContentTypes.FirstOrDefault(c => c.Key == x.Item1.Key); + var containers = getEntityContainers(contentType)?.ToArray(); + var containerPath = $"/{(containers != null && containers.Any() ? $"{string.Join("/", containers.Select(c => c.Name))}/" : null)}"; + x.Item1.AdditionalData["containerPath"] = containerPath; + return x; }) .ToList(); From d01dfb4ba67e0c5ac46e9bf65d4e8dcd724531ce Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 29 Nov 2018 16:45:39 +0100 Subject: [PATCH 25/26] Add search to move media dialog --- .../src/views/media/media.move.controller.js | 33 +++++++++++++++-- .../src/views/media/move.html | 37 ++++++++++++++----- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js index 77e59cbbfa..9213337a0d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js @@ -6,6 +6,14 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", $scope.dialogTreeEventHandler = $({}); var node = dialogOptions.currentNode; + $scope.busy = false; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + } $scope.treeModel = { hideHeader: false } @@ -44,9 +52,24 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", } } - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { event: evt, node: result }); + }; + + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; $scope.move = function () { $scope.busy = true; @@ -79,6 +102,10 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", }); }; + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.$on('$destroy', function () { $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); diff --git a/src/Umbraco.Web.UI.Client/src/views/media/move.html b/src/Umbraco.Web.UI.Client/src/views/media/move.html index 88c64f1992..b76f9a6fb2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/move.html @@ -25,15 +25,34 @@
- - + + + +
+ + + + +
+ + +
Date: Wed, 19 Dec 2018 18:19:51 +0100 Subject: [PATCH 26/26] Add "Move" option for deleted items in the content tree (#3772) --- .../content/content.restore.controller.js | 133 ++++++++++++++---- .../src/views/content/restore.html | 92 ++++++++++-- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 6 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 +- .../umbraco/config/lang/en_us.xml | 4 +- .../Trees/ContentTreeController.cs | 1 + 6 files changed, 189 insertions(+), 51 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js index 62eaa6fca6..cb6a85fbb1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js @@ -1,22 +1,95 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController", - function ($scope, relationResource, contentResource, navigationService, appState, treeService, localizationService) { + function ($scope, relationResource, contentResource, navigationService, appState, treeService, userService) { var dialogOptions = $scope.dialogOptions; - var node = dialogOptions.currentNode; + $scope.source = _.clone(dialogOptions.currentNode); - $scope.error = null; + $scope.error = null; + $scope.loading = true; + $scope.moving = false; $scope.success = false; - relationResource.getByChildId(node.id, "relateParentDocumentOnDelete").then(function (data) { + $scope.dialogTreeEventHandler = $({}); + $scope.searchInfo = { + showSearch: false, + results: [], + selectedSearchResults: [] + } + $scope.treeModel = { + hideHeader: false + } + userService.getCurrentUser().then(function (userData) { + $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1; + }); - if (data.length == 0) { - $scope.success = false; - $scope.error = { - errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), - data: { - Message: localizationService.localize('recycleBin_noRestoreRelation') - } - } + function nodeSelectHandler(ev, args) { + + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + + } + + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } + + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.results = []; + } + + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { event: evt, node: result }); + }; + + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); + + // Mini list view + $scope.selectListViewNode = function (node) { + node.selected = node.selected === true ? false : true; + nodeSelectHandler({}, { node: node }); + }; + + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + + function openMiniListView(node) { + $scope.miniListView = node; + } + + relationResource.getByChildId($scope.source.id, "relateParentDocumentOnDelete").then(function (data) { + $scope.loading = false; + + if (!data.length) { + $scope.moving = true; return; } @@ -25,39 +98,37 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController" if ($scope.relation.parentId == -1) { $scope.target = { id: -1, name: "Root" }; - } else { - contentResource.getById($scope.relation.parentId).then(function (data) { - $scope.target = data; + } else { + $scope.loading = true; + + contentResource.getById($scope.relation.parentId).then(function (data) { + $scope.loading = false; + $scope.target = data; // make sure the target item isn't in the recycle bin - if($scope.target.path.indexOf("-20") !== -1) { - $scope.error = { - errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), - data: { - Message: localizationService.localize('recycleBin_restoreUnderRecycled').then(function (value) { - value.replace('%0%', $scope.target.name); - }) - } - }; - $scope.success = false; - } - + if ($scope.target.path.indexOf("-20") !== -1) { + $scope.moving = true; + $scope.target = null; + } }, function (err) { - $scope.success = false; + $scope.loading = false; $scope.error = err; }); } }, function (err) { - $scope.success = false; + $scope.loading = false; $scope.error = err; }); $scope.restore = function () { + $scope.loading = true; + // this code was copied from `content.move.controller.js` - contentResource.move({ parentId: $scope.target.id, id: node.id }) + contentResource.move({ parentId: $scope.target.id, id: $scope.source.id }) .then(function (path) { + $scope.loading = false; $scope.success = true; //first we need to remove the node that launched the dialog @@ -78,7 +149,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController" }); }, function (err) { - $scope.success = false; + $scope.loading = false; $scope.error = err; }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/restore.html b/src/Umbraco.Web.UI.Client/src/views/content/restore.html index e99e2eb251..3218b8f1b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/restore.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/restore.html @@ -1,26 +1,92 @@
-
+
+ + -

- Restore {{currentNode.name}} under {{target.name}}? -

+
+
+
{{error.errorMsg}}
+
{{error.data.Message}}
+
+
-
-
{{error.errorMsg}}
-
{{error.data.Message}}
-
+
+
+ {{source.name}} + was restored under + was moved underneath + {{target.name}} +
+ +
-
-

{{currentNode.name}} was moved underneath {{target.name}}

- -
+
+ +

+ Restore {{source.name}} under {{target.name}}? +

+ +
+ +
+
+
+
Cannot automatically restore this item
+
There is no location where this item can be automatically restored. You can move the item manually using the tree below.
+
+
+ +
+ + + +
+ + + + +
+ + +
+
+ + + + +
- diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 92a38692a2..4f61bf3ee4 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1622,8 +1622,8 @@ Mange hilsner fra Umbraco robotten Slettet indhold med Id: {0} Relateret til original "parent" med id: {1} Slettet medie med Id: {0} relateret til original "parent" / mappe med id: {1} - Kan ikke automatisk genoprette dette dokument/medie - Der findes ikke nogen "Genopret" relation for dette dokument/medie. Brug "Flyt" muligheden fra menuen for at flytte det manuelt. - Det dokument/medie du ønsker at genoprette under ('%0%') er i skraldespanden. Brug "Flyt" muligheden fra menuen for at flytte det manuelt. + Kan ikke automatisk genoprette dette element + Der er ikke nogen placering hvor dette element automatisk kan genoprettes. Du kan flytte elementet manuelt i træet nedenfor. + blev genoprettet under diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 5a2a28f549..76cf013b40 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -2224,7 +2224,7 @@ To manage your website, simply open the Umbraco back office and start adding con Trashed content with Id: {0} related to original parent content with Id: {1} Trashed media with Id: {0} related to original parent media item with Id: {1} Cannot automatically restore this item - There is no 'restore' relation found for this node. Use the Move menu item to move it manually. - The item you want to restore it under ('%0%') is in the recycle bin. Use the Move menu item to move the item manually. + There is no location where this item can be automatically restored. You can move the item manually using the tree below. + was restored under diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index fc5ae4428f..47fef59019 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -2217,8 +2217,8 @@ To manage your website, simply open the Umbraco back office and start adding con Trashed content with Id: {0} related to original parent content with Id: {1} Trashed media with Id: {0} related to original parent media item with Id: {1} Cannot automatically restore this item - There is no 'restore' relation found for this node. Use the Move menu item to move it manually. - The item you want to restore it under ('%0%') is in the recycle bin. Use the Move menu item to move the item manually. + There is no location where this item can be automatically restored. You can move the item manually using the tree below. + was restored under Select your notifications for diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index c3fe7df483..293cc3d36a 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -255,6 +255,7 @@ namespace Umbraco.Web.Trees { var menu = new MenuItemCollection(); menu.Items.Add(ui.Text("actions", ActionRestore.Instance.Alias)); + menu.Items.Add(ui.Text("actions", ActionMove.Instance.Alias)); menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true);