From 0abe685d7f9d5c37315ba830fe00fb1b4ee0ccf2 Mon Sep 17 00:00:00 2001 From: elitsa Date: Mon, 3 Dec 2018 09:00:44 +0100 Subject: [PATCH 01/80] 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/80] 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/80] 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/80] 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/80] 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/80] 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/80] 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/80] 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/80] 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/80] 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/80] 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/80] 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 c8593c23e6a60dfac11114cd4db44bea0154d65c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 12 Dec 2018 11:59:08 +0100 Subject: [PATCH 13/80] show correct createDate for variants, only show contextual variant status, only show context variant urls, add variant log colors --- .../content/umbcontentnodeinfo.directive.js | 89 ++++++++++--------- .../content/umb-content-node-info.html | 23 ++--- 2 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 31e847f0f6..232e31daee 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -10,6 +10,8 @@ var auditTrailLoaded = false; var labels = {}; scope.publishStatus = []; + scope.currentVariant = null; + scope.currentUrls = []; scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; scope.allowChangeDocumentType = false; @@ -17,6 +19,18 @@ function onInit() { + // set currentVariant + scope.currentVariant = _.find(scope.node.variants, (v) => v.active); + + // find the urls for the currently selected language + if(scope.node.variants.length > 1) { + // nodes with variants + scope.currentUrls = _.filter(scope.node.urls, (url) => scope.currentVariant.language.culture === url.culture); + } else { + // invariant nodes + scope.currentUrls = scope.node.urls; + } + // if there are any infinite editors open we are in infinite editing scope.isInfiniteMode = editorService.getNumberOfEditors() > 0 ? true : false; @@ -50,7 +64,7 @@ labels.unsavedChanges = data[5]; labels.doctypeChangeWarning = data[6]; - setNodePublishStatus(scope.node); + setNodePublishStatus(); }); @@ -58,6 +72,9 @@ "id": scope.node.id }; + // make sure dates are formatted to the user's locale + formatDatesToLocal(); + // get available templates scope.availableTemplates = scope.node.allowedTemplates; @@ -221,12 +238,13 @@ function setAuditTrailLogTypeColor(auditTrail) { angular.forEach(auditTrail, function (item) { - switch (item.logType) { case "Publish": + case "PublishVariant": item.logTypeColor = "success"; break; case "Unpublish": + case "UnpublishVariant": case "Delete": item.logTypeColor = "danger"; break; @@ -236,51 +254,40 @@ }); } - function setNodePublishStatus(node) { + function setNodePublishStatus() { - scope.publishStatus = []; + scope.status = {}; // deleted node - if (node.trashed === true) { - scope.publishStatus.push({ - label: labels.deleted, - color: "danger" - }); + if (scope.node.trashed === true) { + scope.status.color = "danger"; return; } - if (node.variants) { - for (var i = 0; i < node.variants.length; i++) { - - var variant = node.variants[i]; - - var status = { - culture: variant.language ? variant.language.culture : null - }; - - if (variant.state === "NotCreated") { - status.label = labels.notCreated; - status.color = "gray"; - } - else if (variant.state === "Draft") { - // draft node - status.label = labels.unpublished; - status.color = "gray"; - } - else if (variant.state === "Published") { - // published node - status.label = labels.published; - status.color = "success"; - } - else if (variant.state === "PublishedPendingChanges") { - // published node with pending changes - status.label = labels.publishedPendingChanges; - status.color = "success"; - } - - scope.publishStatus.push(status); - } + // variant status + if (scope.currentVariant.state === "NotCreated") { + // not created + scope.status.color = "gray"; } + else if (scope.currentVariant.state === "Draft") { + // draft node + scope.status.color = "gray"; + } + else if (scope.currentVariant.state === "Published") { + // published node + scope.status.color = "success"; + } + else if (scope.currentVariant.state === "PublishedPendingChanges") { + // published node with pending changes + scope.status.color = "success"; + } + } + + function formatDatesToLocal() { + // get current backoffice user and format dates + userService.getCurrentUser().then(function (currentUser) { + scope.currentVariant.createDateFormatted = dateHelper.getLocalDate(scope.currentVariant.createDate, currentUser.locale, 'LLL'); + }); } // load audit trail and redirects when on the info tab @@ -306,7 +313,7 @@ auditTrailLoaded = false; loadAuditTrail(); loadRedirectUrls(); - setNodePublishStatus(scope.node); + setNodePublishStatus(); } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index 429cceb4d9..e880fc0f4e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -2,21 +2,19 @@
- + @@ -62,8 +60,9 @@
- +
@@ -96,7 +95,6 @@ class="history-item__badge" size="xs" color="{{item.logTypeColor}}"> - {{ item.logType }} @@ -130,16 +128,13 @@ -
- {{status.culture}}: - - {{status.label}} - -
+ + +
- {{node.createDateFormatted}} by {{ node.owner.name }} + {{currentVariant.createDateFormatted}} From 4f6f959d7fd9ba96bb2ea926ac02cb0fec62a401 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 12 Dec 2018 13:12:13 +0100 Subject: [PATCH 14/80] Add missing translations --- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 775b40fd0c..498744f5cf 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -147,8 +147,9 @@ Viewing for Content deleted Content unpublished - Content saved and Published - Content saved and published for languages: %0% + Content unpublished for languages: %0% + Content published + Content published for languages: %0% Content saved Content saved for languages: %0% Content moved @@ -165,6 +166,7 @@ Save Delete Unpublish + Unpublish Rollback Send To Publish Send To Publish From defa231c4bcdc54689386f98dd0270793476eee4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 12 Dec 2018 14:29:20 +0100 Subject: [PATCH 15/80] add label for history --- .../components/content/umbcontentnodeinfo.directive.js | 8 ++++++-- .../views/components/content/umb-content-node-info.html | 3 ++- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 1 + 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 232e31daee..7b56bb2bd0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -51,7 +51,9 @@ "content_publishedPendingChanges", "content_notCreated", "prompt_unsavedChanges", - "prompt_doctypeChangeWarning" + "prompt_doctypeChangeWarning", + "general_history", + "auditTrails_historyIncludingVariants" ]; localizationService.localizeMany(keys) @@ -63,7 +65,9 @@ labels.notCreated = data[4]; labels.unsavedChanges = data[5]; labels.doctypeChangeWarning = data[6]; - + + scope.historyLabel = scope.node.variants && scope.node.variants.length === 1 ? data[7] : data[8]; + setNodePublishStatus(); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index e880fc0f4e..b37e3c7ba4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -41,7 +41,8 @@ + title="{{historyLabel}}"> + Send To Publish Send To Publish Sort + History (all variants) To change the document type for the selected content, first select from the list of valid types for this location. From 9649e61f17085aa30f04c10d7725fc47cb780d79 Mon Sep 17 00:00:00 2001 From: Aaron Powell Date: Thu, 13 Dec 2018 11:54:32 +1100 Subject: [PATCH 16/80] Fixing #3866 - making a few methods public --- src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index ea818c60e3..a54b997cb8 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -417,7 +417,7 @@ namespace Umbraco.Core.Migrations.Install #region Database Schema - internal DatabaseSchemaResult ValidateDatabaseSchema() + public DatabaseSchemaResult ValidateDatabaseSchema() { using (var scope = _scopeProvider.CreateScope()) { @@ -442,7 +442,7 @@ namespace Umbraco.Core.Migrations.Install return _databaseSchemaValidationResult; } - internal Result CreateDatabaseSchemaAndData() + public Result CreateDatabaseSchemaAndData() { using (var scope = _scopeProvider.CreateScope()) { @@ -522,7 +522,7 @@ namespace Umbraco.Core.Migrations.Install } // This assumes all of the previous checks are done! - internal Result UpgradeSchemaAndData() + public Result UpgradeSchemaAndData() { try { @@ -630,7 +630,7 @@ namespace Umbraco.Core.Migrations.Install }; } - internal class Result + public class Result { public bool RequiresUpgrade { get; set; } public string Message { get; set; } From 33b9870aecb794cf6c534a2e70915a74838fd3e6 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sun, 16 Dec 2018 21:52:45 +0100 Subject: [PATCH 17/80] Only perform save when previewing - not a publish --- .../src/common/directives/components/content/edit.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 407416f2a1..f1e2150579 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -815,7 +815,7 @@ //ensure the save flag is set selectedVariant.save = true; - performSave({ saveMethod: contentResource.publish, action: "save" }).then(function (data) { + performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function (data) { previewWindow.location.href = redirect; }, function (err) { //validation issues .... From ebc689032d787e0cd6532a1ffce15a5d3ad77472 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 17 Dec 2018 10:48:33 +0100 Subject: [PATCH 18/80] fix dictionary pickers in code editors --- .../insertcodesnippet/insertcodesnippet.controller.js | 2 +- .../src/views/partialviewmacros/edit.controller.js | 2 +- .../src/views/partialviews/edit.controller.js | 2 +- .../src/views/templates/edit.controller.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.controller.js index 526d076048..2b40d496f5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.controller.js @@ -77,7 +77,7 @@ var emptyStateMessage = values[2]; var dictionaryItemPicker = { - section: "settings", + section: "translation", treeAlias: "dictionary", entityType: "dictionary", multiPicker: false, diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js index afc00bb7e2..372cecb36c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviewmacros/edit.controller.js @@ -201,7 +201,7 @@ var emptyStateMessage = values[1]; var dictionaryPicker = { - section: "settings", + section: "translation", treeAlias: "dictionary", entityType: "dictionary", multiPicker: false, diff --git a/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js index ff14ea0ebd..292898814d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialviews/edit.controller.js @@ -207,7 +207,7 @@ var emptyStateMessage = values[1]; var dictionaryItem = { - section: "settings", + section: "translation", treeAlias: "dictionary", entityType: "dictionary", multiPicker: false, diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index 8ac9dc78e8..1f6fb8863a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -438,7 +438,7 @@ var emptyStateMessage = values[1]; var dictionaryItem = { - section: "settings", + section: "translation", treeAlias: "dictionary", entityType: "dictionary", multiPicker: false, From 4068dca93874eaacbef7d249284fc84fee0752d4 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 17 Dec 2018 13:55:08 +0100 Subject: [PATCH 19/80] reenable infinite editing animations --- .../components/editor/umbeditors.directive.js | 67 ++++++------------- .../less/components/editor/umb-editor.less | 2 +- 2 files changed, 20 insertions(+), 49 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js index e68c2bbc9a..58afc72f6c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditors.directive.js @@ -25,25 +25,6 @@ // shown so we don't animate a lot of editors which aren't necessary var moveEditors = editorsElement.querySelectorAll('.umb-editor:nth-last-child(-n+'+ allowedNumberOfVisibleEditors +')'); - // this is a temporary fix because the animations doesn't perform well - // TODO: fix animation and remove this - moveEditors.forEach(function(editor, index){ - - // resize the small editors to 100% so we can easily slide them - if(editor.classList.contains("umb-editor--small")) { - editor.style.width = "100%"; - } - - // set left position to indent the editors - if(scope.editors.length >= allowedNumberOfVisibleEditors) { - $(editor).css({"left": index * editorIndent}); - } else { - $(editor).css({"left": (index + 1) * editorIndent}); - } - - }); - - /* // collapse open editors before opening the new one var collapseEditorAnimation = anime({ targets: moveEditors, @@ -61,9 +42,8 @@ return (index + 1) * editorIndent; }, easing: 'easeInOutQuint', - duration: 500 + duration: 300 }); - */ // push the new editor to the dom scope.editors.push(editor); @@ -95,7 +75,7 @@ translateX: [100 + '%', 0], opacity: [0, 1], easing: 'easeInOutQuint', - duration: 400, + duration: 300, complete: function() { $timeout(function(){ editor.animating = false; @@ -126,11 +106,12 @@ $timeout(function(){ scope.editors.splice(-1,1); removeOverlayFromPrevEditor(); - expandEditors(); }); } }); + expandEditors(); + }); } @@ -142,42 +123,32 @@ var editorsElement = el[0]; // only select the editors which are allowed to be // shown so we don't animate a lot of editors which aren't necessary - var moveEditors = editorsElement.querySelectorAll('.umb-editor:nth-last-child(-n+'+ 4 +')'); + // as the last element hasn't been removed from the dom yet we have to select the last four and then skip the last child (as it is the one closing). + var moveEditors = editorsElement.querySelectorAll('.umb-editor:nth-last-child(-n+'+ allowedNumberOfVisibleEditors + 1 +'):not(:last-child)'); + var editorWidth = editorsElement.offsetWidth; - // this is a temporary fix because the animations doesn't perform well - // TODO: fix animation and remove this - moveEditors.forEach(function(editor, index){ - // set left position - $(editor).css({"left": (index + 1) * editorIndent}); - - // if the new top editor is a small editor we will have to resize it back to the right size on - // move it all the way to the right side - if(editor.classList.contains("umb-editor--small") && index + 1 === moveEditors.length) { - editor.style.width = "500px"; - $(editor).css({"left": ""}); - } - }); - - // We need to figure out how to performance optimize this - // TODO: optimize animation - /* var expandEditorAnimation = anime({ targets: moveEditors, left: function(el, index, length){ - return (index + 1) * editorIndent; + // move the editor all the way to the right if the top one is a small + if(el.classList.contains("umb-editor--small")) { + // only change the size if it is the editor on top + if(index + 1 === length) { + return editorWidth - 500; + } + } else { + return (index + 1) * editorIndent; + } }, width: function(el, index, length) { - if(el.classList.contains("umb-editor--small")) { + // set the correct size if the top editor is of type "small" + if(el.classList.contains("umb-editor--small") && index + 1 === length) { return "500px"; } }, easing: 'easeInOutQuint', - duration: 500, - completed: function() { - - } + duration: 300 }); - */ }); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less index 640a276443..321fabadad 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor/umb-editor.less @@ -42,6 +42,6 @@ bottom: 0; right: 0; left: 0; - background: rgba(0,0,0,0.2); + background: rgba(0,0,0,0.4); z-index: @zIndexEditor; } \ No newline at end of file From c020383c22a4514c93dcedffb4d42feb8346e3cb Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 13 Dec 2018 13:47:09 +0100 Subject: [PATCH 20/80] Remove confusing LazyCollectionBuilderBase.Remove --- .../Composing/LazyCollectionBuilderBase.cs | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs index 52e5a764fd..ee263f458f 100644 --- a/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs +++ b/src/Umbraco.Core/Composing/LazyCollectionBuilderBase.cs @@ -75,37 +75,6 @@ namespace Umbraco.Core.Composing return This; } - /// - /// Removes a type from the collection. - /// - /// The type to remove. - /// The builder. - public TBuilder Remove() - where T : TItem - { - Configure(types => - { - var type = typeof(T); - if (types.Contains(type)) types.Remove(type); - }); - return This; - } - - /// - /// Removes a type from the collection. - /// - /// The type to remove. - /// The builder. - public TBuilder Remove(Type type) - { - Configure(types => - { - EnsureType(type, "remove"); - if (types.Contains(type)) types.Remove(type); - }); - return This; - } - /// /// Adds a types producer to the collection. /// From 8152862ad2b9d086b953996083453b3f15678313 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 13 Dec 2018 15:08:12 +0100 Subject: [PATCH 21/80] Fix issue with figuring out whether a published content version is draft --- .../PublishedContent/IPublishedContent.cs | 2 +- .../PublishedContentWrapped.cs | 2 +- .../CaseInsensitiveDictionaryConverter.cs | 25 +++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Published/NestedContentTests.cs | 2 +- .../PublishedContentDataTableTests.cs | 2 +- .../SolidPublishedSnapshot.cs | 3 +- .../TestHelpers/Stubs/TestPublishedContent.cs | 2 +- .../Models/PublishedContentBase.cs | 2 +- .../PublishedCache/NuCache/ContentCache.cs | 42 +++++------ .../NuCache/DataSource/ContentNestedData.cs | 3 + .../NuCache/DataSource/CultureVariation.cs | 3 + .../NuCache/DataSource/DatabaseDataSource.cs | 41 ++++++----- .../NuCache/Navigable/NavigableContent.cs | 2 +- .../NuCache/PublishedContent.cs | 23 +++++- .../NuCache/PublishedSnapshotService.cs | 3 +- .../PublishedCache/PublishedMember.cs | 2 +- .../DictionaryPublishedContent.cs | 2 +- .../XmlPublishedCache/XmlPublishedContent.cs | 9 +-- src/Umbraco.Web/PublishedContentExtensions.cs | 72 ++++++------------- src/Umbraco.Web/umbraco.presentation/page.cs | 4 +- 21 files changed, 136 insertions(+), 111 deletions(-) create mode 100644 src/Umbraco.Core/Serialization/CaseInsensitiveDictionaryConverter.cs diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 5b604eff3f..0c049e81bf 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -151,7 +151,7 @@ namespace Umbraco.Core.Models.PublishedContent /// is the edited version) or false (document is published, and has not been edited, and /// what is returned is the published version). /// - bool IsDraft { get; } + bool IsDraft(string culture = null); // fixme - consider having an IsPublished flag too // so that when IsDraft is true, we can check whether there is a published version? diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index 5bdeb3685d..6a69d0b9e1 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -109,7 +109,7 @@ namespace Umbraco.Core.Models.PublishedContent public virtual PublishedItemType ItemType => _content.ItemType; /// - public virtual bool IsDraft => _content.IsDraft; + public virtual bool IsDraft(string culture = null) => _content.IsDraft(culture); #endregion diff --git a/src/Umbraco.Core/Serialization/CaseInsensitiveDictionaryConverter.cs b/src/Umbraco.Core/Serialization/CaseInsensitiveDictionaryConverter.cs new file mode 100644 index 0000000000..a92d562a52 --- /dev/null +++ b/src/Umbraco.Core/Serialization/CaseInsensitiveDictionaryConverter.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Newtonsoft.Json.Converters; + +namespace Umbraco.Core.Serialization +{ + /// + /// Marks dictionaries so they are deserialized as case-insensitive. + /// + /// + /// [JsonConverter(typeof(CaseInsensitiveDictionaryConverter{PropertyData[]}))] + /// public Dictionary{string, PropertyData[]} PropertyData {{ get; set; }} + /// + public class CaseInsensitiveDictionaryConverter : CustomCreationConverter + { + public override bool CanWrite => false; + + public override bool CanRead => true; + + public override bool CanConvert(Type objectType) => typeof(IDictionary).IsAssignableFrom(objectType); + + public override IDictionary Create(Type objectType) => new Dictionary(StringComparer.OrdinalIgnoreCase); + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d69fab18a1..cd5c005de7 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -1312,6 +1312,7 @@ + diff --git a/src/Umbraco.Tests/Published/NestedContentTests.cs b/src/Umbraco.Tests/Published/NestedContentTests.cs index 920fa2acd5..cf00345b65 100644 --- a/src/Umbraco.Tests/Published/NestedContentTests.cs +++ b/src/Umbraco.Tests/Published/NestedContentTests.cs @@ -262,7 +262,7 @@ namespace Umbraco.Tests.Published // ReSharper disable UnassignedGetOnlyAutoProperty public override PublishedItemType ItemType { get; } - public override bool IsDraft { get; } + public override bool IsDraft(string culture = null) => false; public override IPublishedContent Parent { get; } public override IEnumerable Children { get; } public override PublishedContentType ContentType { get; } diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs index a640423515..aa9e7e4918 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentDataTableTests.cs @@ -216,7 +216,7 @@ namespace Umbraco.Tests.PublishedContent public DateTime UpdateDate { get; set; } public Guid Version { get; set; } public int Level { get; set; } - public bool IsDraft { get; set; } + public bool IsDraft(string culture = null) => false; public IEnumerable Properties { get; set; } diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index efd1c6ae8b..0c4059ca7c 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -162,7 +162,6 @@ namespace Umbraco.Tests.PublishedContent WriterId = CreatorId = 0; CreateDate = UpdateDate = DateTime.Now; Version = Guid.Empty; - IsDraft = false; ContentType = contentType; } @@ -192,7 +191,7 @@ namespace Umbraco.Tests.PublishedContent public string GetUrl(string culture = null) => throw new NotSupportedException(); public PublishedItemType ItemType { get { return PublishedItemType.Content; } } - public bool IsDraft { get; set; } + public bool IsDraft(string culture = null) => false; #endregion diff --git a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs index 0faf1537b3..9c0bb61cb3 100644 --- a/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs +++ b/src/Umbraco.Tests/TestHelpers/Stubs/TestPublishedContent.cs @@ -47,7 +47,7 @@ namespace Umbraco.Tests.TestHelpers.Stubs public string Url { get; set; } public string GetUrl(string culture = null) => throw new NotSupportedException(); public PublishedItemType ItemType => ContentType.ItemType; - public bool IsDraft { get; set; } + public bool IsDraft(string culture = null) => false; public IPublishedContent Parent { get; set; } public IEnumerable Children { get; set; } diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 1b8128b4c0..667cf145bd 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -142,7 +142,7 @@ namespace Umbraco.Web.Models public abstract PublishedItemType ItemType { get; } /// - public abstract bool IsDraft { get; } + public abstract bool IsDraft(string culture = null); #endregion diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs index edef545d2a..0197d2d640 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentCache.cs @@ -147,7 +147,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var urlSegment = n.GetUrlSegment(culture); var hasDomains = _domainHelper.NodeHasDomains(n.Id); while (hasDomains == false && n != null) // n is null at root - { + { // no segment indicates this is not published when this is a variant if (urlSegment.IsNullOrWhiteSpace()) return null; @@ -173,7 +173,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var path = "/" + string.Join("/", pathParts); // will be "/" or "/foo" or "/foo/bar" etc //prefix the root node id containing the domain if it exists (this is a standard way of creating route paths) //and is done so that we know the ID of the domain node for the path - var route = (n?.Id.ToString(CultureInfo.InvariantCulture) ?? "") + path; + var route = (n?.Id.ToString(CultureInfo.InvariantCulture) ?? "") + path; return route; } @@ -223,24 +223,14 @@ namespace Umbraco.Web.PublishedCache.NuCache public override IPublishedContent GetById(bool preview, int contentId) { - var n = _snapshot.Get(contentId); - if (n == null) return null; - - // both .Draft and .Published cannot be null at the same time - return preview - ? n.Draft ?? GetPublishedContentAsPreviewing(n.Published) - : n.Published; + var node = _snapshot.Get(contentId); + return GetNodePublishedContent(node, preview); } public override IPublishedContent GetById(bool preview, Guid contentId) { - var n = _snapshot.Get(contentId); - if (n == null) return null; - - // both .Draft and .Published cannot be null at the same time - return preview - ? n.Draft ?? GetPublishedContentAsPreviewing(n.Published) - : n.Published; + var node = _snapshot.Get(contentId); + return GetNodePublishedContent(node, preview); } public override bool HasById(bool preview, int contentId) @@ -274,14 +264,24 @@ namespace Umbraco.Web.PublishedCache.NuCache var c = _snapshot.GetAtRoot(); // both .Draft and .Published cannot be null at the same time - return c.Select(n => preview - ? n.Draft ?? GetPublishedContentAsPreviewing(n.Published) - : n.Published).WhereNotNull().OrderBy(x => x.SortOrder); + return c.Select(n => GetNodePublishedContent(n, preview)).WhereNotNull().OrderBy(x => x.SortOrder); + } + + private static IPublishedContent GetNodePublishedContent(ContentNode node, bool preview) + { + if (node == null) + return null; + + // both .Draft and .Published cannot be null at the same time + + return preview + ? node.Draft ?? GetPublishedContentAsDraft(node.Published) + : node.Published; } // gets a published content as a previewing draft, if preview is true // this is for published content when previewing - internal static IPublishedContent GetPublishedContentAsPreviewing(IPublishedContent content /*, bool preview*/) + private static IPublishedContent GetPublishedContentAsDraft(IPublishedContent content /*, bool preview*/) { if (content == null /*|| preview == false*/) return null; //content; @@ -290,7 +290,7 @@ namespace Umbraco.Web.PublishedCache.NuCache // case we need to unwrap to get to the original IPublishedContentOrMedia. var inner = PublishedContent.UnwrapIPublishedContent(content); - return inner.AsPreviewingModel(); + return inner.AsDraft(); } public override bool HasContent(bool preview) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs index be3e813275..0f120024cc 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/ContentNestedData.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json; using System.Collections.Generic; +using Umbraco.Core.Serialization; namespace Umbraco.Web.PublishedCache.NuCache.DataSource { @@ -9,9 +10,11 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource internal class ContentNestedData { [JsonProperty("properties")] + [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] public Dictionary PropertyData { get; set; } [JsonProperty("cultureData")] + [JsonConverter(typeof(CaseInsensitiveDictionaryConverter))] public Dictionary CultureData { get; set; } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs index 50a2adaeb8..c6e603f5a9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/CultureVariation.cs @@ -13,5 +13,8 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource [JsonProperty("date")] public DateTime Date { get; set; } + + [JsonProperty("isDraft")] + public bool IsDraft { get; set; } } } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs index a143997fab..4531d37b2b 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/DataSource/DatabaseDataSource.cs @@ -182,27 +182,30 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource ContentData d = null; ContentData p = null; - if (dto.EditData == null) + if (dto.Edited) { - if (Debugger.IsAttached) - throw new Exception("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); - Current.Logger.Warn("Missing cmsContentNu edited content for node {NodeId}, consider rebuilding.", dto.Id); - } - else - { - var nested = DeserializeNestedData(dto.EditData); - - d = new ContentData + if (dto.EditData == null) { - Name = dto.EditName, - Published = false, - TemplateId = dto.EditTemplateId, - VersionId = dto.VersionId, - VersionDate = dto.EditVersionDate, - WriterId = dto.EditWriterId, - Properties = nested.PropertyData, - CultureInfos = nested.CultureData - }; + if (Debugger.IsAttached) + throw new Exception("Missing cmsContentNu edited content for node " + dto.Id + ", consider rebuilding."); + Current.Logger.Warn("Missing cmsContentNu edited content for node {NodeId}, consider rebuilding.", dto.Id); + } + else + { + var nested = DeserializeNestedData(dto.EditData); + + d = new ContentData + { + Name = dto.EditName, + Published = false, + TemplateId = dto.EditTemplateId, + VersionId = dto.VersionId, + VersionDate = dto.EditVersionDate, + WriterId = dto.EditWriterId, + Properties = nested.PropertyData, + CultureInfos = nested.CultureData + }; + } } if (dto.Published) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs index ae34d0cb32..51badc8b9a 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/Navigable/NavigableContent.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.PublishedCache.NuCache.Navigable XmlString(i++, _content.WriterId), XmlString(i++, _content.CreatorId), XmlString(i++, _content.UrlSegment), - XmlString(i, _content.IsDraft) + XmlString(i, _content.IsDraft()) }; } diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index a4610e82db..36e5698e32 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -273,8 +273,27 @@ namespace Umbraco.Web.PublishedCache.NuCache /// public override PublishedItemType ItemType => _contentNode.ContentType.ItemType; + // fixme + // was => _contentData.Published == false; /// - public override bool IsDraft => _contentData.Published == false; + public override bool IsDraft(string culture = null) + { + // if this is the 'published' published content, nothing can be draft + if (_contentData.Published) + return false; + + // not the 'published' published content, and does not vary = must be draft + if (!ContentType.VariesByCulture()) + return true; + + // handle context culture + if (culture == null) + culture = VariationContextAccessor?.VariationContext?.Culture ?? ""; + + // not the 'published' published content, and varies + // = depends on the culture + return _contentData.CultureInfos.TryGetValue(culture, out var cvar) && cvar.IsDraft; + } #endregion @@ -410,7 +429,7 @@ namespace Umbraco.Web.PublishedCache.NuCache private string AsPreviewingCacheKey => _asPreviewingCacheKey ?? (_asPreviewingCacheKey = CacheKeys.PublishedContentAsPreviewing(Key)); // used by ContentCache - internal IPublishedContent AsPreviewingModel() + internal IPublishedContent AsDraft() { if (IsPreviewing) return this; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 100833216e..4d6115f02d 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -1206,7 +1206,8 @@ namespace Umbraco.Web.PublishedCache.NuCache foreach (var (culture, info) in infos) { - cultureData[culture] = new CultureVariation { Name = info.Name, Date = content.GetUpdateDate(culture) ?? DateTime.MinValue }; + var cultureIsDraft = !published && content is IContent d && d.IsCultureEdited(culture); + cultureData[culture] = new CultureVariation { Name = info.Name, Date = content.GetUpdateDate(culture) ?? DateTime.MinValue, IsDraft = cultureIsDraft }; } } diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 44ce2328b7..56c8f440d8 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -79,7 +79,7 @@ namespace Umbraco.Web.PublishedCache public override PublishedItemType ItemType => PublishedItemType.Member; - public override bool IsDraft => false; + public override bool IsDraft(string culture = null) => false; public override IPublishedContent Parent => null; diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs index 7a8ce65ae3..4453fe7321 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/DictionaryPublishedContent.cs @@ -176,7 +176,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache public override int Level => _level; - public override bool IsDraft => false; + public override bool IsDraft(string culture = null) => false; public override IEnumerable Properties => _properties; diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs index 3c143a6066..af867cc089 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/XmlPublishedContent.cs @@ -221,13 +221,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache } } - public override bool IsDraft + public override bool IsDraft(string culture = null) { - get - { - EnsureNodeInitialized(); - return _isDraft; - } + EnsureNodeInitialized(); + return _isDraft; // bah } public override IEnumerable Properties diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 2bc0d7be3f..eb47fd2f2d 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -141,50 +141,35 @@ namespace Umbraco.Web #endregion - // fixme - .HasValue() and .Value() refactoring - in progress - see exceptions below - - #region HasValue + #region HasValue, Value, Value /// /// Gets a value indicating whether the content has a value for a property identified by its alias. /// /// The content. /// The property alias. - /// A value indicating whether to navigate the tree upwards until a property with a value is found. + /// The variation language. + /// The variation segment. + /// Optional fallback strategy. /// A value indicating whether the content has a value for the property identified by the alias. - /// Returns true if GetProperty(alias, recurse) is not null and GetProperty(alias, recurse).HasValue is true. - public static bool HasValue(this IPublishedContent content, string alias, bool recurse) + /// Returns true if HasValue is true, or a fallback strategy can provide a value. + public static bool HasValue(this IPublishedContent content, string alias, string culture = null, string segment = null, Fallback fallback = default) { - throw new NotImplementedException("WorkInProgress"); + var property = content.GetProperty(alias); - //var prop = content.GetProperty(alias, recurse); - //return prop != null && prop.HasValue(); + // if we have a property, and it has a value, return that value + if (property != null && property.HasValue(culture, segment)) + return true; + + // else let fallback try to get a value + // fixme - really? + if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, null, out _)) + return true; + + // else... no + return false; } - /// - /// Returns one of two strings depending on whether the content has a value for a property identified by its alias. - /// - /// The content. - /// The property alias. - /// A value indicating whether to navigate the tree upwards until a property with a value is found. - /// The value to return if the content has a value for the property. - /// The value to return if the content has no value for the property. - /// Either or depending on whether the content - /// has a value for the property identified by the alias. - public static IHtmlString HasValue(this IPublishedContent content, string alias, bool recurse, - string valueIfTrue, string valueIfFalse = null) - { - throw new NotImplementedException("WorkInProgress"); - - //return content.HasValue(alias, recurse) - // ? new HtmlString(valueIfTrue) - // : new HtmlString(valueIfFalse ?? string.Empty); - } - - #endregion - - #region Value - /// /// Gets the value of a content's property identified by its alias, if it exists, otherwise a default value. /// @@ -207,15 +192,14 @@ namespace Umbraco.Web if (PublishedValueFallback.TryGetValue(content, alias, culture, segment, fallback, defaultValue, out var value)) return value; + if (property == null) + return null; + // else... if we have a property, at least let the converter return its own - // vision of 'no value' (could be an empty enumerable) - otherwise, default - return property?.GetValue(culture, segment); + // vision of 'no value' (could be an empty enumerable) + return property.GetValue(culture, segment); } - #endregion - - #region Value - /// /// Gets the value of a content's property identified by its alias, converted to a specified type. /// @@ -383,16 +367,6 @@ namespace Umbraco.Web return recursive && content.IsComposedOf(docTypeAlias); } - public static bool IsNull(this IPublishedContent content, string alias, bool recurse) - { - return content.HasValue(alias, recurse) == false; - } - - public static bool IsNull(this IPublishedContent content, string alias) - { - return content.HasValue(alias) == false; - } - #endregion #region IsSomething: equality diff --git a/src/Umbraco.Web/umbraco.presentation/page.cs b/src/Umbraco.Web/umbraco.presentation/page.cs index e8d395881c..219e2101be 100644 --- a/src/Umbraco.Web/umbraco.presentation/page.cs +++ b/src/Umbraco.Web/umbraco.presentation/page.cs @@ -467,9 +467,9 @@ namespace umbraco get { return PublishedItemType.Content; } } - public bool IsDraft + public bool IsDraft(string culture = null) { - get { throw new NotImplementedException(); } + throw new NotImplementedException(); } public IPublishedContent Parent From d8d04c54e669dd6ab8de65b0e3943af5a55059f2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 18 Dec 2018 08:33:54 +0100 Subject: [PATCH 22/80] Fix IOHelper for debugging other apps --- src/Umbraco.Core/IO/IOHelper.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/IO/IOHelper.cs b/src/Umbraco.Core/IO/IOHelper.cs index 2f1675e08a..7773f378a5 100644 --- a/src/Umbraco.Core/IO/IOHelper.cs +++ b/src/Umbraco.Core/IO/IOHelper.cs @@ -284,11 +284,12 @@ namespace Umbraco.Core.IO binFolder = Path.Combine(GetRootDirectorySafe(), "bin"); -#if DEBUG + // do this all the time (no #if DEBUG) because Umbraco release + // can be used in tests by an app (eg Deploy) being debugged var debugFolder = Path.Combine(binFolder, "debug"); if (Directory.Exists(debugFolder)) return debugFolder; -#endif + var releaseFolder = Path.Combine(binFolder, "release"); if (Directory.Exists(releaseFolder)) return releaseFolder; From 53c67fad632d29dc522c738b01d3f81b0e355967 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 18 Dec 2018 10:39:39 +0100 Subject: [PATCH 23/80] Rework for #3866 --- .../Migrations/Install/DatabaseBuilder.cs | 154 +++++++++--------- .../Install/DatabaseSchemaCreator.cs | 7 +- .../Install/DatabaseSchemaResult.cs | 146 +++-------------- .../Persistence/SchemaValidationTest.cs | 1 - .../Controllers/InstallApiController.cs | 2 +- .../InstallSteps/DatabaseConfigureStep.cs | 6 +- .../InstallSteps/DatabaseInstallStep.cs | 2 +- .../InstallSteps/DatabaseUpgradeStep.cs | 16 +- 8 files changed, 108 insertions(+), 226 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index a54b997cb8..ccf2365deb 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -33,6 +33,9 @@ namespace Umbraco.Core.Migrations.Install private DatabaseSchemaResult _databaseSchemaValidationResult; + /// + /// Initializes a new instance of the class. + /// public DatabaseBuilder(IScopeProvider scopeProvider, IGlobalSettings globalSettings, IUmbracoDatabaseFactory databaseFactory, IRuntimeState runtime, ILogger logger, IMigrationBuilder migrationBuilder, IKeyValueService keyValueService, PostMigrationCollection postMigrations) { _scopeProvider = scopeProvider; @@ -49,23 +52,20 @@ namespace Umbraco.Core.Migrations.Install /// /// Gets a value indicating whether the database is configured. It does not necessarily - /// mean that it is possible to connect, nor that Umbraco is installed, nor - /// up-to-date. + /// mean that it is possible to connect, nor that Umbraco is installed, nor up-to-date. /// public bool IsDatabaseConfigured => _databaseFactory.Configured; /// - /// Gets a value indicating whether it is possible to connect to the database. + /// Gets a value indicating whether it is possible to connect to the configured database. + /// It does not necessarily mean that Umbraco is installed, nor up-to-date. /// - public bool CanConnect => _databaseFactory.CanConnect; + public bool CanConnectToDatabase => _databaseFactory.CanConnect; - // that method was originally created by Per in DatabaseHelper- tests the db connection for install - // fixed by Shannon to not-ignore the provider - // fixed by Stephan as part of the v8 persistence cleanup, now using provider names + SqlCe exception - // moved by Stephan to DatabaseBuilder - // probably needs to be cleaned up - - public bool CheckConnection(string databaseType, string connectionString, string server, string database, string login, string password, bool integratedAuth) + /// + /// Verifies whether a it is possible to connect to a database. + /// + public bool CanConnect(string databaseType, string connectionString, string server, string database, string login, string password, bool integratedAuth) { // we do not test SqlCE connection if (databaseType.InvariantContains("sqlce")) @@ -93,7 +93,7 @@ namespace Umbraco.Core.Migrations.Install return DbConnectionExtensions.IsConnectionAvailable(connectionString, providerName); } - public bool HasSomeNonDefaultUser() + internal bool HasSomeNonDefaultUser() { using (var scope = _scopeProvider.CreateScope()) { @@ -417,17 +417,24 @@ namespace Umbraco.Core.Migrations.Install #region Database Schema - public DatabaseSchemaResult ValidateDatabaseSchema() + /// + /// Validates the database schema. + /// + /// + /// This assumes that the database exists and the connection string is + /// configured and it is possible to connect to the database. + /// + internal DatabaseSchemaResult ValidateSchema() { using (var scope = _scopeProvider.CreateScope()) { - var result = ValidateDatabaseSchema(scope); + var result = ValidateSchema(scope); scope.Complete(); return result; } } - private DatabaseSchemaResult ValidateDatabaseSchema(IScope scope) + private DatabaseSchemaResult ValidateSchema(IScope scope) { if (_databaseFactory.Configured == false) return new DatabaseSchemaResult(_databaseFactory.SqlContext.SqlSyntax); @@ -442,17 +449,24 @@ namespace Umbraco.Core.Migrations.Install return _databaseSchemaValidationResult; } - public Result CreateDatabaseSchemaAndData() + /// + /// Creates the database schema and inserts initial data. + /// + /// + /// This assumes that the database exists and the connection string is + /// configured and it is possible to connect to the database. + /// + public Result CreateSchemaAndData() { using (var scope = _scopeProvider.CreateScope()) { - var result = CreateDatabaseSchemaAndData(scope); + var result = CreateSchemaAndData(scope); scope.Complete(); return result; } } - private Result CreateDatabaseSchemaAndData(IScope scope) + private Result CreateSchemaAndData(IScope scope) { try { @@ -468,28 +482,14 @@ namespace Umbraco.Core.Migrations.Install // If MySQL, we're going to ensure that database calls are maintaining proper casing as to remove the necessity for checks // for case insensitive queries. In an ideal situation (which is what we're striving for), all calls would be case sensitive. - - /* - var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database); - if (supportsCaseInsensitiveQueries == false) - { - message = "

 

The database you're trying to use does not support case insensitive queries.
We currently do not support these types of databases.

" + - "

You can fix this by changing the following setting in your my.ini file in your MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

Note: Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - - return new Result { Message = message, Success = false, Percentage = "15" }; - } - */ - - var message = GetResultMessageForMySql(); - var schemaResult = ValidateDatabaseSchema(); - var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); + var message = database.DatabaseType.IsMySql() ? ResultMessageForMySql : ""; + var schemaResult = ValidateSchema(); + var hasInstalledVersion = schemaResult.DetermineHasInstalledVersion(); + //var installedSchemaVersion = schemaResult.DetermineInstalledVersion(); + //var hasInstalledVersion = !installedSchemaVersion.Equals(new Version(0, 0, 0)); //If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing - if (string.IsNullOrEmpty(_globalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0))) + if (string.IsNullOrEmpty(_globalSettings.ConfigurationStatus) && !hasInstalledVersion) { if (_runtime.Level == RuntimeLevel.Run) throw new Exception("Umbraco is already configured!"); @@ -521,7 +521,14 @@ namespace Umbraco.Core.Migrations.Install } } - // This assumes all of the previous checks are done! + /// + /// Upgrades the database schema and data by running migrations. + /// + /// + /// This assumes that the database exists and the connection string is + /// configured and it is possible to connect to the database. + /// Runs whichever migrations need to run. + /// public Result UpgradeSchemaAndData() { try @@ -534,7 +541,7 @@ namespace Umbraco.Core.Migrations.Install _logger.Info("Database upgrade started"); - var message = GetResultMessageForMySql(); + var message = _scopeProvider.SqlContext.DatabaseType.IsMySql() ? ResultMessageForMySql : ""; // upgrade var upgrader = new UmbracoUpgrader(); @@ -554,47 +561,14 @@ namespace Umbraco.Core.Migrations.Install } } - private string GetResultMessageForMySql() - { - if (_databaseFactory.GetType() == typeof(MySqlSyntaxProvider)) - { - return "

 

Congratulations, the database step ran successfully!

" + - "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + - "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + - "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - } - return string.Empty; - } - - /* - private string GetResultMessageForMySql(bool? supportsCaseInsensitiveQueries) - { - if (supportsCaseInsensitiveQueries == null) - { - return "

 

Warning! Could not check if your database type supports case insensitive queries.
We currently do not support these databases that do not support case insensitive queries.

" + - "

You can check this by looking for the following setting in your my.ini file in your MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

Note: Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - } - if (SqlSyntax.GetType() == typeof(MySqlSyntaxProvider)) - { - return "

 

Congratulations, the database step ran successfully!

" + - "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + - "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + - "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + - "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + - "
lower_case_table_names=1

" + - "

For more technical information on case sensitivity in MySQL, have a look at " + - "the documentation on the subject

"; - } - return string.Empty; - }*/ + private const string ResultMessageForMySql = "

 

Congratulations, the database step ran successfully!

" + + "

Note: You're using MySQL and the database instance you're connecting to seems to support case insensitive queries.

" + + "

However, your hosting provider may not support this option. Umbraco does not currently support MySQL installs that do not support case insensitive queries

" + + "

Make sure to check with your hosting provider if they support case insensitive queries as well.

" + + "

They can check this by looking for the following setting in the my.ini file in their MySQL installation directory:

" + + "
lower_case_table_names=1

" + + "

For more technical information on case sensitivity in MySQL, have a look at " + + "the documentation on the subject

"; private Attempt CheckReadyForInstall() { @@ -630,11 +604,29 @@ namespace Umbraco.Core.Migrations.Install }; } + /// + /// Represents the result of a database creation or upgrade. + /// public class Result { + /// + /// Gets or sets ... fixme + /// public bool RequiresUpgrade { get; set; } + + /// + /// Gets or sets the message returned by the operation. + /// public string Message { get; set; } + + /// + /// Gets or sets a value indicating whether the operation succeeded. + /// public bool Success { get; set; } + + /// + /// Gets or sets ... fixme + /// public string Percentage { get; set; } } diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs index 64be8161f2..eba5e61390 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaCreator.cs @@ -138,9 +138,8 @@ namespace Umbraco.Core.Migrations.Install { var result = new DatabaseSchemaResult(SqlSyntax); - //get the db index defs - result.DbIndexDefinitions = SqlSyntax.GetDefinedIndexes(_database) - .Select(x => new DbIndexDefinition(x)).ToArray(); + result.IndexDefinitions.AddRange(SqlSyntax.GetDefinedIndexes(_database) + .Select(x => new DbIndexDefinition(x))); result.TableDefinitions.AddRange(OrderedTables .Select(x => DefinitionFactory.GetTableDefinition(x, SqlSyntax))); @@ -279,7 +278,7 @@ namespace Umbraco.Core.Migrations.Install { //These are just column indexes NOT constraints or Keys //var colIndexesInDatabase = result.DbIndexDefinitions.Where(x => x.IndexName.InvariantStartsWith("IX_")).Select(x => x.IndexName).ToList(); - var colIndexesInDatabase = result.DbIndexDefinitions.Select(x => x.IndexName).ToList(); + var colIndexesInDatabase = result.IndexDefinitions.Select(x => x.IndexName).ToList(); var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList(); //Add valid and invalid index differences to the result object diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs index 0ec27cf0b1..4c68addebc 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseSchemaResult.cs @@ -2,153 +2,55 @@ using System.Collections.Generic; using System.Linq; using System.Text; -using Umbraco.Core.Configuration; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.SqlSyntax; namespace Umbraco.Core.Migrations.Install { - public class DatabaseSchemaResult + /// + /// Represents ... + /// + internal class DatabaseSchemaResult { - private readonly ISqlSyntaxProvider _sqlSyntax; + private readonly bool _isMySql; public DatabaseSchemaResult(ISqlSyntaxProvider sqlSyntax) { - _sqlSyntax = sqlSyntax; + _isMySql = sqlSyntax is MySqlSyntaxProvider; + Errors = new List>(); TableDefinitions = new List(); ValidTables = new List(); ValidColumns = new List(); ValidConstraints = new List(); ValidIndexes = new List(); + IndexDefinitions = new List(); } - public List> Errors { get; set; } + public List> Errors { get; } - public List TableDefinitions { get; set; } + public List TableDefinitions { get; } - public List ValidTables { get; set; } + // fixme TableDefinitions are those that should be there, IndexDefinitions are those that... are in DB? + internal List IndexDefinitions { get; } - public List ValidColumns { get; set; } + public List ValidTables { get; } - public List ValidConstraints { get; set; } + public List ValidColumns { get; } - public List ValidIndexes { get; set; } + public List ValidConstraints { get; } - internal IEnumerable DbIndexDefinitions { get; set; } + public List ValidIndexes { get; } /// - /// Determines the version of the currently installed database by detecting the current database structure + /// Determines whether the database contains an installed version. /// - /// - /// A with Major and Minor values for - /// non-empty database, otherwise "0.0.0" for empty databases. - /// - public Version DetermineInstalledVersion() + /// + /// A database contains an installed version when it contains at least one valid table. + /// + public bool DetermineHasInstalledVersion() { - // v8 = kill versions older than 7 - - //If (ValidTables.Count == 0) database is empty and we return -> new Version(0, 0, 0); - if (ValidTables.Count == 0) - return new Version(0, 0, 0); - - // FIXME - but the whole detection is borked really - return new Version(8, 0, 0); - - //If Errors is empty or if TableDefinitions tables + columns correspond to valid tables + columns then we're at current version - if (Errors.Any() == false || - (TableDefinitions.All(x => ValidTables.Contains(x.Name)) - && TableDefinitions.SelectMany(definition => definition.Columns).All(x => ValidColumns.Contains(x.Name)))) - return UmbracoVersion.Current; - - //If Errors contains umbracoApp or umbracoAppTree its pre-6.0.0 -> new Version(4, 10, 0); - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoApp") || x.Item2.InvariantEquals("umbracoAppTree")))) - { - //If Errors contains umbracoUser2app or umbracoAppTree foreignkey to umbracoApp exists its pre-4.8.0 -> new Version(4, 7, 0); - if (Errors.Any(x => - x.Item1.Equals("Constraint") - && (x.Item2.InvariantContains("umbracoUser2app_umbracoApp") - || x.Item2.InvariantContains("umbracoAppTree_umbracoApp")))) - { - return new Version(4, 7, 0); - } - - return new Version(4, 8, 0); - } - - //if the error is for umbracoServer - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoServer")))) - { - return new Version(6, 0, 0); - } - - //if the error indicates a problem with the column cmsMacroProperty.macroPropertyType then it is not version 7 - // since these columns get removed in v7 - if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMacroProperty,macroPropertyType")))) - { - //if the error is for this IX_umbracoNodeTrashed which is added in 6.2 AND in 7.1 but we do not have the above columns - // then it must mean that we aren't on 6.2 so must be 6.1 - if (Errors.Any(x => x.Item1.Equals("Index") && (x.Item2.InvariantEquals("IX_umbracoNodeTrashed")))) - { - return new Version(6, 1, 0); - } - else - { - //if there are no errors for that index, then the person must have 6.2 installed - return new Version(6, 2, 0); - } - } - - //if the error indicates a problem with the constraint FK_cms-OBSOLETE-Content_cmsContentType_nodeId then it is not version 7.2 - // since this gets added in 7.2.0 so it must be the previous version - if (Errors.Any(x => x.Item1.Equals("Constraint") && (x.Item2.InvariantEquals("FK_cms-OBSOLETE-Content_cmsContentType_nodeId")))) - { - return new Version(7, 0, 0); - } - - //if the error is for umbracoAccess it must be the previous version to 7.3 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoAccess")))) - { - return new Version(7, 2, 0); - } - - //if the error is for cms-OBSOLETE-PropertyData.dataDecimal it must be the previous version to 7.4 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cms-OBSOLETE-PropertyData,dataDecimal")))) - { - return new Version(7, 3, 0); - } - - //if the error is for umbracoRedirectUrl it must be the previous version to 7.5 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoRedirectUrl")))) - { - return new Version(7, 4, 0); - } - - //if the error indicates a problem with the column cmsMacroProperty.uniquePropertyId then it is not version 7.6 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMacroProperty,uniquePropertyId")))) - { - return new Version(7, 5, 0); - } - - //if the error is for umbracoUserGroup it must be the previous version to 7.7 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoUserStartNode")))) - { - return new Version(7, 6, 0); - } - - //if the error is for cmsMedia it must be the previous version to 7.8 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Table") && (x.Item2.InvariantEquals("umbracoMedia")))) - { - return new Version(7, 7, 0); - } - - //if the error is for isSensitive column it must be the previous version to 7.9 since that is when it is added - if (Errors.Any(x => x.Item1.Equals("Column") && (x.Item2.InvariantEquals("cmsMemberType,isSensitive")))) - { - return new Version(7, 8, 0); - } - - return UmbracoVersion.Current; + return ValidTables.Count > 0; } /// @@ -200,9 +102,9 @@ namespace Umbraco.Core.Migrations.Install sb.AppendLine(" "); } - if (_sqlSyntax is MySqlSyntaxProvider) + if (_isMySql) { - sb.AppendLine("Please note that the constraints could not be validated because the current dataprovider is MySql."); + sb.AppendLine("Please note that the constraints could not be validated because the current data provider is MySql."); } return sb.ToString(); diff --git a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs index b8f1fab918..2c875d6afc 100644 --- a/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs +++ b/src/Umbraco.Tests/Persistence/SchemaValidationTest.cs @@ -26,7 +26,6 @@ namespace Umbraco.Tests.Persistence // Assert Assert.That(result.Errors.Count, Is.EqualTo(0)); - Assert.AreEqual(result.DetermineInstalledVersion(), UmbracoVersion.Current); } } } diff --git a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs index 387360163a..81b1aac217 100644 --- a/src/Umbraco.Web/Install/Controllers/InstallApiController.cs +++ b/src/Umbraco.Web/Install/Controllers/InstallApiController.cs @@ -41,7 +41,7 @@ namespace Umbraco.Web.Install.Controllers public bool PostValidateDatabaseConnection(DatabaseModel model) { - var canConnect = _databaseBuilder.CheckConnection(model.DatabaseType.ToString(), model.ConnectionString, model.Server, model.DatabaseName, model.Login, model.Password, model.IntegratedAuth); + var canConnect = _databaseBuilder.CanConnect(model.DatabaseType.ToString(), model.ConnectionString, model.Server, model.DatabaseName, model.Login, model.Password, model.IntegratedAuth); return canConnect; } diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs index a54b64733f..2fe6c0ceda 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseConfigureStep.cs @@ -1,7 +1,6 @@ using System; using System.Configuration; using Umbraco.Core; -using Umbraco.Core.Configuration; using Umbraco.Core.Logging; using Umbraco.Core.Migrations.Install; using Umbraco.Web.Install.Models; @@ -29,7 +28,7 @@ namespace Umbraco.Web.Install.InstallSteps database = new DatabaseModel(); } - if (_databaseBuilder.CheckConnection(database.DatabaseType.ToString(), database.ConnectionString, database.Server, database.DatabaseName, database.Login, database.Password, database.IntegratedAuth) == false) + if (_databaseBuilder.CanConnect(database.DatabaseType.ToString(), database.ConnectionString, database.Server, database.DatabaseName, database.Login, database.Password, database.IntegratedAuth) == false) { throw new InstallException("Could not connect to the database"); } @@ -79,8 +78,7 @@ namespace Umbraco.Web.Install.InstallSteps try { //Since a connection string was present we verify the db can connect and query - var result = _databaseBuilder.ValidateDatabaseSchema(); - result.DetermineInstalledVersion(); + _ = _databaseBuilder.ValidateSchema(); return false; } catch (Exception ex) diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs index c4cad38072..a9daee6e95 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseInstallStep.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Install.InstallSteps if (_runtime.Level == RuntimeLevel.Run) throw new Exception("Umbraco is already configured!"); - var result = _databaseBuilder.CreateDatabaseSchemaAndData(); + var result = _databaseBuilder.CreateSchemaAndData(); if (result.Success == false) { diff --git a/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs b/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs index c078caf906..8283eb6bef 100644 --- a/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/DatabaseUpgradeStep.cs @@ -63,18 +63,10 @@ namespace Umbraco.Web.Install.InstallSteps if (_databaseBuilder.IsConnectionStringConfigured(databaseSettings)) { - //Since a connection string was present we verify whether this is an upgrade or an empty db - var result = _databaseBuilder.ValidateDatabaseSchema(); - - var determinedVersion = result.DetermineInstalledVersion(); - if (determinedVersion.Equals(new Version(0, 0, 0))) - { - //Fresh install - return false; - } - - //Upgrade - return true; + // a connection string was present, determine whether this is an install/upgrade + // return true (upgrade) if there is an installed version, else false (install) + var result = _databaseBuilder.ValidateSchema(); + return result.DetermineHasInstalledVersion(); } //no connection string configured, probably a fresh install From 792bf4003710a88fdfb0f54dbf18ae16049b9f98 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 18 Dec 2018 10:44:59 +0100 Subject: [PATCH 24/80] Rework for #3866 --- src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs index ccf2365deb..ef6b3b720b 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseBuilder.cs @@ -610,7 +610,7 @@ namespace Umbraco.Core.Migrations.Install public class Result { /// - /// Gets or sets ... fixme + /// Gets or sets a value indicating whether an upgrade is required. /// public bool RequiresUpgrade { get; set; } @@ -625,7 +625,7 @@ namespace Umbraco.Core.Migrations.Install public bool Success { get; set; } /// - /// Gets or sets ... fixme + /// Gets or sets an install progress pseudo-percentage. /// public string Percentage { get; set; } } From a19a58796fb04f6aa62b308689f8726ebd6b232f Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 18 Dec 2018 22:02:39 +1100 Subject: [PATCH 25/80] Changes IUrlProvider to return UrlInfo --- .../Routing/UrlsProviderWithDomainsTests.cs | 4 +- .../TestControllerActivatorBase.cs | 2 +- .../Testing/TestingTests/MockTests.cs | 2 +- .../Web/TemplateUtilitiesTests.cs | 2 +- src/Umbraco.Web/Routing/AliasUrlProvider.cs | 29 ++++----- .../Routing/CustomRouteUrlProvider.cs | 16 +++-- src/Umbraco.Web/Routing/DefaultUrlProvider.cs | 27 ++++---- src/Umbraco.Web/Routing/IUrlProvider.cs | 4 +- src/Umbraco.Web/Routing/UrlInfo.cs | 65 ++++++++++++++++--- src/Umbraco.Web/Routing/UrlProvider.cs | 10 +-- .../Routing/UrlProviderExtensions.cs | 46 ++++--------- 11 files changed, 118 insertions(+), 89 deletions(-) diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index c6bd0fdb88..2f1bef3b9a 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -418,8 +418,8 @@ namespace Umbraco.Tests.Routing foreach (var x in result) Console.WriteLine(x); Assert.AreEqual(2, result.Length); - Assert.IsTrue(result.Contains("http://domain1a.com/en/1001-1-1/")); - Assert.IsTrue(result.Contains("http://domain1b.com/en/1001-1-1/")); + Assert.AreEqual(result[0].Text, "http://domain1a.com/en/1001-1-1/"); + Assert.AreEqual(result[1].Text, "http://domain1b.com/en/1001-1-1/"); } } } diff --git a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs index 2cf64f04d1..7152622b07 100644 --- a/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs +++ b/src/Umbraco.Tests/TestHelpers/ControllerTesting/TestControllerActivatorBase.cs @@ -159,7 +159,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting var urlHelper = new Mock(); urlHelper.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns("/hello/world/1234"); + .Returns(UrlInfo.Url("/hello/world/1234")); var membershipHelper = new MembershipHelper(umbCtx, Mock.Of(), Mock.Of()); diff --git a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs index 51855f7e19..5b93de0c09 100644 --- a/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs +++ b/src/Umbraco.Tests/Testing/TestingTests/MockTests.cs @@ -75,7 +75,7 @@ namespace Umbraco.Tests.Testing.TestingTests var urlProviderMock = new Mock(); urlProviderMock.Setup(provider => provider.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns("/hello/world/1234"); + .Returns(UrlInfo.Url("/hello/world/1234")); var urlProvider = urlProviderMock.Object; var theUrlProvider = new UrlProvider(umbracoContext, new [] { urlProvider }, umbracoContext.VariationContextAccessor); diff --git a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs index 1e6229fa4c..d98afa6bcc 100644 --- a/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs +++ b/src/Umbraco.Tests/Web/TemplateUtilitiesTests.cs @@ -79,7 +79,7 @@ namespace Umbraco.Tests.Web var testUrlProvider = new Mock(); testUrlProvider .Setup(x => x.GetUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlProviderMode mode, string culture, Uri url) => "/my-test-url"); + .Returns((UmbracoContext umbCtx, IPublishedContent content, UrlProviderMode mode, string culture, Uri url) => UrlInfo.Url("/my-test-url")); var globalSettings = SettingsForTests.GenerateMockGlobalSettings(); diff --git a/src/Umbraco.Web/Routing/AliasUrlProvider.cs b/src/Umbraco.Web/Routing/AliasUrlProvider.cs index 8c851d139f..4c879e931f 100644 --- a/src/Umbraco.Web/Routing/AliasUrlProvider.cs +++ b/src/Umbraco.Web/Routing/AliasUrlProvider.cs @@ -31,7 +31,7 @@ namespace Umbraco.Web.Routing #region GetUrl /// - public string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) { return null; // we have nothing to say } @@ -51,13 +51,14 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) + public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { var node = umbracoContext.ContentCache.GetById(id); - if (node == null) return Enumerable.Empty(); + if (node == null) + yield break; if (!node.HasProperty(Constants.Conventions.Content.UrlAlias)) - return Enumerable.Empty(); + yield break; var domainHelper = umbracoContext.GetDomainHelper(_siteDomainHelper); @@ -82,20 +83,19 @@ namespace Umbraco.Web.Routing // the content finder may work, depending on the 'current' culture, // but there's no way we can return something meaningful here if (varies) - return Enumerable.Empty(); + yield break; var umbracoUrlName = node.Value(Constants.Conventions.Content.UrlAlias); if (string.IsNullOrWhiteSpace(umbracoUrlName)) - return Enumerable.Empty(); + yield break; var path = "/" + umbracoUrlName; var uri = new Uri(path, UriKind.Relative); - return new[] { UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString() }; + yield return UrlInfo.Url(UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString()); } else { // some domains: one url per domain, which is "/" - var result = new List(); foreach(var domainUri in domainUris) { // if the property is invariant, get the invariant value, url is "/" @@ -108,14 +108,13 @@ namespace Umbraco.Web.Routing ? node.Value(Constants.Conventions.Content.UrlAlias, culture: domainUri.Culture.Name) : node.Value(Constants.Conventions.Content.UrlAlias); - if (!string.IsNullOrWhiteSpace(umbracoUrlName)) - { - var path = "/" + umbracoUrlName; - var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); - result.Add(UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString()); - } + if (string.IsNullOrWhiteSpace(umbracoUrlName)) + continue; + + var path = "/" + umbracoUrlName; + var uri = new Uri(CombinePaths(domainUri.Uri.GetLeftPart(UriPartial.Path), path)); + yield return UrlInfo.Url(UriUtility.UriFromUmbraco(uri, _globalSettings, _requestConfig).ToString(), domainUri.Culture.Name); } - return result; } } diff --git a/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs b/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs index ce1fc1ffab..ae17ff6258 100644 --- a/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs +++ b/src/Umbraco.Web/Routing/CustomRouteUrlProvider.cs @@ -15,7 +15,7 @@ namespace Umbraco.Web.Routing /// /// This will return the URL that is returned by the assigned custom if this is a custom route /// - public string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) { if (umbracoContext?.PublishedRequest?.PublishedContent == null) return null; if (umbracoContext.HttpContext?.Request?.RequestContext?.RouteData?.DataTokens == null) return null; @@ -26,13 +26,15 @@ namespace Umbraco.Web.Routing //NOTE: This looks like it might cause an infinite loop because PublishedContentBase.Url calls into UmbracoContext.Current.UrlProvider.GetUrl which calls back into the IUrlProvider pipeline // but the specific purpose of this is that a developer is using their own IPublishedContent that returns a specific Url and doesn't go back into the UrlProvider pipeline. //TODO: We could put a try/catch here just in case, else we could do some reflection checking to see if the implementation is PublishedContentBase and the Url property is not overridden. - return content.Id == umbracoContext.PublishedRequest.PublishedContent.Id - ? umbracoContext.PublishedRequest.PublishedContent.GetUrl(culture) - : null; + return UrlInfo.Url( + content.Id == umbracoContext.PublishedRequest.PublishedContent.Id + ? umbracoContext.PublishedRequest.PublishedContent.GetUrl(culture) + : null, + culture); } /// - /// This always returns null because this url provider is used purely to deal with Umbraco custom routes with + /// This always returns an empty result because this url provider is used purely to deal with Umbraco custom routes with /// UmbracoVirtualNodeRouteHandler, we really only care about the normal URL so that RedirectToCurrentUmbracoPage() works /// with SurfaceControllers /// @@ -40,9 +42,9 @@ namespace Umbraco.Web.Routing /// /// /// - public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) + public IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { - return null; + yield break; } } } diff --git a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs index 61437b6640..5045b1af96 100644 --- a/src/Umbraco.Web/Routing/DefaultUrlProvider.cs +++ b/src/Umbraco.Web/Routing/DefaultUrlProvider.cs @@ -29,7 +29,7 @@ namespace Umbraco.Web.Routing #region GetUrl /// - public virtual string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) + public virtual UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current) { if (!current.IsAbsoluteUri) throw new ArgumentException("Current url must be absolute.", nameof(current)); @@ -39,7 +39,7 @@ namespace Umbraco.Web.Routing return GetUrlFromRoute(route, umbracoContext, content.Id, current, mode, culture); } - internal string GetUrlFromRoute(string route, UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode, string culture) + internal UrlInfo GetUrlFromRoute(string route, UmbracoContext umbracoContext, int id, Uri current, UrlProviderMode mode, string culture) { if (string.IsNullOrWhiteSpace(route)) { @@ -58,7 +58,9 @@ namespace Umbraco.Web.Routing : domainHelper.DomainForNode(int.Parse(route.Substring(0, pos)), current, culture); // assemble the url from domainUri (maybe null) and path - return AssembleUrl(domainUri, path, current, mode).ToString(); + var url = AssembleUrl(domainUri, path, current, mode).ToString(); + + return UrlInfo.Url(url, culture); } #endregion @@ -76,10 +78,11 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - public virtual IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) + public virtual IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current) { var node = umbracoContext.ContentCache.GetById(id); - if (node == null) return Enumerable.Empty(); + if (node == null) + yield break; var domainHelper = umbracoContext.GetDomainHelper(_siteDomainHelper); @@ -94,13 +97,14 @@ namespace Umbraco.Web.Routing // no domains = exit if (domainUris ==null) - return Enumerable.Empty(); + yield break; - var result = new List(); foreach (var d in domainUris) { + var culture = d?.Culture?.Name; + //although we are passing in culture here, if any node in this path is invariant, it ignores the culture anyways so this is ok - var route = umbracoContext.ContentCache.GetRouteById(id, d?.Culture?.Name); + var route = umbracoContext.ContentCache.GetRouteById(id, culture); if (route == null) continue; //need to strip off the leading ID for the route if it exists (occurs if the route is for a node with a domain assigned) @@ -109,9 +113,8 @@ namespace Umbraco.Web.Routing var uri = new Uri(CombinePaths(d.Uri.GetLeftPart(UriPartial.Path), path)); uri = UriUtility.UriFromUmbraco(uri, _globalSettings, _requestSettings); - result.Add(uri.ToString()); + yield return UrlInfo.Url(uri.ToString(), culture); } - return result; } #endregion @@ -146,7 +149,7 @@ namespace Umbraco.Web.Routing uri = new Uri(path, UriKind.Relative); break; default: - throw new ArgumentOutOfRangeException("mode"); + throw new ArgumentOutOfRangeException(nameof(mode)); } } else // a domain was found @@ -169,7 +172,7 @@ namespace Umbraco.Web.Routing uri = new Uri(CombinePaths(domainUri.Uri.AbsolutePath, path), UriKind.Relative); break; default: - throw new ArgumentOutOfRangeException("mode"); + throw new ArgumentOutOfRangeException(nameof(mode)); } } diff --git a/src/Umbraco.Web/Routing/IUrlProvider.cs b/src/Umbraco.Web/Routing/IUrlProvider.cs index 0f102dceb8..55d39880d6 100644 --- a/src/Umbraco.Web/Routing/IUrlProvider.cs +++ b/src/Umbraco.Web/Routing/IUrlProvider.cs @@ -24,7 +24,7 @@ namespace Umbraco.Web.Routing /// when no culture is specified, the current culture. /// If the provider is unable to provide a url, it should return null. /// - string GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current); + UrlInfo GetUrl(UmbracoContext umbracoContext, IPublishedContent content, UrlProviderMode mode, string culture, Uri current); /// /// Gets the other urls of a published content. @@ -37,6 +37,6 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current); + IEnumerable GetOtherUrls(UmbracoContext umbracoContext, int id, Uri current); } } diff --git a/src/Umbraco.Web/Routing/UrlInfo.cs b/src/Umbraco.Web/Routing/UrlInfo.cs index 01dbe4a0e1..a795f1577b 100644 --- a/src/Umbraco.Web/Routing/UrlInfo.cs +++ b/src/Umbraco.Web/Routing/UrlInfo.cs @@ -1,4 +1,5 @@ -using System.Runtime.Serialization; +using System; +using System.Runtime.Serialization; namespace Umbraco.Web.Routing { @@ -6,13 +7,25 @@ namespace Umbraco.Web.Routing /// Represents infos for a url. /// [DataContract(Name = "urlInfo", Namespace = "")] - public class UrlInfo + public class UrlInfo : IEquatable { + + /// + /// Creates a instance representing a true url. + /// + public static UrlInfo Url(string text, string culture = null) => new UrlInfo(text, true, culture); + + /// + /// Creates a instance representing a message. + /// + public static UrlInfo Message(string text, string culture = null) => new UrlInfo(text, false, culture); + /// /// Initializes a new instance of the class. /// - public UrlInfo(string text, bool isUrl, string culture) + private UrlInfo(string text, bool isUrl, string culture) { + if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(text)); IsUrl = isUrl; Text = text; Culture = culture; @@ -38,13 +51,47 @@ namespace Umbraco.Web.Routing public string Text { get; } /// - /// Creates a instance representing a true url. + /// Checks equality /// - public static UrlInfo Url(string text, string culture = null) => new UrlInfo(text, true, culture); + /// + /// + /// + /// Compare both culture and Text as invariant strings since URLs are not case sensitive, nor are culture names within Umbraco + /// + public bool Equals(UrlInfo other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return string.Equals(Culture, other.Culture, StringComparison.InvariantCultureIgnoreCase) && IsUrl == other.IsUrl && string.Equals(Text, other.Text, StringComparison.InvariantCultureIgnoreCase); + } - /// - /// Creates a instance representing a message. - /// - public static UrlInfo Message(string text, string culture = null) => new UrlInfo(text, false, culture); + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((UrlInfo) obj); + } + + public override int GetHashCode() + { + unchecked + { + var hashCode = (Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Culture) : 0); + hashCode = (hashCode * 397) ^ IsUrl.GetHashCode(); + hashCode = (hashCode * 397) ^ (Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(Text) : 0); + return hashCode; + } + } + + public static bool operator ==(UrlInfo left, UrlInfo right) + { + return Equals(left, right); + } + + public static bool operator !=(UrlInfo left, UrlInfo right) + { + return !Equals(left, right); + } } } diff --git a/src/Umbraco.Web/Routing/UrlProvider.cs b/src/Umbraco.Web/Routing/UrlProvider.cs index b265d48923..491deee9e3 100644 --- a/src/Umbraco.Web/Routing/UrlProvider.cs +++ b/src/Umbraco.Web/Routing/UrlProvider.cs @@ -202,7 +202,7 @@ namespace Umbraco.Web.Routing var url = _urlProviders.Select(provider => provider.GetUrl(_umbracoContext, content, mode, culture, current)) .FirstOrDefault(u => u != null); - return url ?? "#"; // legacy wants this + return url?.Text ?? "#"; // legacy wants this } internal string GetUrlFromRoute(int id, string route, string culture) @@ -210,7 +210,7 @@ namespace Umbraco.Web.Routing var provider = _urlProviders.OfType().FirstOrDefault(); var url = provider == null ? route // what else? - : provider.GetUrlFromRoute(route, UmbracoContext.Current, id, _umbracoContext.CleanedUmbracoUrl, Mode, culture); + : provider.GetUrlFromRoute(route, UmbracoContext.Current, id, _umbracoContext.CleanedUmbracoUrl, Mode, culture)?.Text; return url ?? "#"; } @@ -228,7 +228,7 @@ namespace Umbraco.Web.Routing /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// The results depend on the current url. /// - public IEnumerable GetOtherUrls(int id) + public IEnumerable GetOtherUrls(int id) { return GetOtherUrls(id, _umbracoContext.CleanedUmbracoUrl); } @@ -243,10 +243,10 @@ namespace Umbraco.Web.Routing /// Other urls are those that GetUrl would not return in the current context, but would be valid /// urls for the node in other contexts (different domain for current request, umbracoUrlAlias...). /// - public IEnumerable GetOtherUrls(int id, Uri current) + public IEnumerable GetOtherUrls(int id, Uri current) { // providers can return null or an empty list or a non-empty list, be prepared - var urls = _urlProviders.SelectMany(provider => provider.GetOtherUrls(_umbracoContext, id, current) ?? Enumerable.Empty()); + var urls = _urlProviders.SelectMany(provider => provider.GetOtherUrls(_umbracoContext, id, current)); return urls; } diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 14b9c0a465..3a0caeec78 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -34,14 +34,11 @@ namespace Umbraco.Web.Routing if (contentService == null) throw new ArgumentNullException(nameof(contentService)); if (logger == null) throw new ArgumentNullException(nameof(logger)); - var urls = new List(); - if (content.Published == false) - { - urls.Add(UrlInfo.Message(textService.Localize("content/itemNotPublished"))); - return urls; - } - + yield return UrlInfo.Message(textService.Localize("content/itemNotPublished")); + + var urls = new HashSet(); + // build a list of urls, for the back-office // which will contain // - the 'main' urls, which is what .Url would return, for each culture @@ -92,37 +89,18 @@ namespace Umbraco.Web.Routing } } - // prepare for de-duplication - var durl = new Dictionary>(); - var dmsg = new Dictionary>(); - foreach (var url in urls) - { - var d = url.IsUrl ? durl : dmsg; - if (!d.TryGetValue(url.Text, out var l)) - d[url.Text] = l = new List(); - l.Add(url); - } - - // deduplicate, order urls first then messages, concatenate cultures (hide if 'all') - var ret = new List(); - foreach (var (text, infos) in durl) - ret.Add(UrlInfo.Url(text, infos.Count == cultures.Count ? null : string.Join(", ", infos.Select(x => x.Culture)))); - foreach (var (text, infos) in dmsg) - ret.Add(UrlInfo.Message(text, infos.Count == cultures.Count ? null : string.Join(", ", infos.Select(x => x.Culture)))); + //return the real urls first, then the messages + foreach (var urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) + foreach (var url in urlGroup) + yield return url; // get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless. // for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them. - // also, we are not dealing with cultures at all - that will have to wait - foreach(var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) - { - if (urls.Any(x => x.IsUrl && x.Text == otherUrl)) continue; - ret.Add(UrlInfo.Url(otherUrl)); - } - - return ret; + foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) + yield return otherUrl; } - private static void HandleCouldNotGetUrl(IContent content, string culture, List urls, IContentService contentService, ILocalizedTextService textService) + private static void HandleCouldNotGetUrl(IContent content, string culture, ICollection urls, IContentService contentService, ILocalizedTextService textService) { // document has a published version yet its url is "#" => a parent must be // unpublished, walk up the tree until we find it, and report. @@ -143,7 +121,7 @@ namespace Umbraco.Web.Routing urls.Add(UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] { parent.Name }), culture)); } - private static bool DetectCollision(IContent content, string url, List urls, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService) + private static bool DetectCollision(IContent content, string url, ICollection urls, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService) { // test for collisions on the 'main' url var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); From 9eecf9898e7e3b9b7f1081f00bdaec8fd680334c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 18 Dec 2018 13:24:52 +0100 Subject: [PATCH 26/80] rebind keyboard shortcuts when infinite editors closes --- .../src/common/services/editor.service.js | 58 ++++++++++++++++++- 1 file changed, 55 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 0dbd27b7a5..5985cc99b1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -78,8 +78,9 @@ (function () { "use strict"; - function editorService(eventsService) { + function editorService(eventsService, keyboardService) { + let editorsKeyboardShorcuts = []; var editors = []; /** @@ -120,6 +121,12 @@ */ function open(editor) { + /* keyboard shortcuts will be overwritten by the new infinite editor + so we need to store the shortcuts for the current editor so they can be rebound + when the infinite editor closes + */ + storeKeyboardShortcuts(); + // set flag so we know when the editor is open in "infinie mode" editor.infiniteMode = true; @@ -142,9 +149,9 @@ * Method to close the latest opened editor */ function close() { - var length = editors.length; - var closedEditor = editors[length - 1]; + // close last opened editor + const closedEditor = editors[editors.length - 1]; editors.splice(-1, 1); var args = { @@ -152,7 +159,12 @@ editor: closedEditor }; + // emit event to let components know an editor has been removed eventsService.emit("appState.editors.close", args); + + // rebind keyboard shortcuts for the new editor in focus + rebindKeyboardShortcuts(); + } /** @@ -652,6 +664,46 @@ open(editor); } + /////////////////////// + + /** + * @ngdoc method + * @name umbraco.services.editorService#storeKeyboardShortcuts + * @methodOf umbraco.services.editorService + * + * @description + * Internal method to keep track of keyboard shortcuts registered + * to each editor so they can be rebound when an editor closes + * + */ + function storeKeyboardShortcuts() { + const shortcuts = angular.copy(keyboardService.keyboardEvent); + editorsKeyboardShorcuts.push(shortcuts); + } + + /** + * @ngdoc method + * @name umbraco.services.editorService#rebindKeyboardShortcuts + * @methodOf umbraco.services.editorService + * + * @description + * Internal method to rebind keyboard shortcuts for the editor in focus + * + */ + function rebindKeyboardShortcuts() { + // find the shortcuts from the previous editor + const lastSetOfShortcutsIndex = editorsKeyboardShorcuts.length - 1; + var lastSetOfShortcuts = editorsKeyboardShorcuts[lastSetOfShortcutsIndex]; + + // rebind shortcuts + for (let [key, value] of Object.entries(lastSetOfShortcuts)) { + keyboardService.bind(key, value.callback, value.opt); + } + + // remove the shortcuts from the collection + editorsKeyboardShorcuts.splice(lastSetOfShortcutsIndex, 1); + } + var service = { getEditors: getEditors, getNumberOfEditors: getNumberOfEditors, From 88d328a4e962cf0d9a77298a35d6e2e981d63109 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 18 Dec 2018 13:40:37 +0100 Subject: [PATCH 27/80] unbind all keyboard shortcuts from the underlaying editor so only keyboard shortcuts from the open editor works --- .../src/common/services/editor.service.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 5985cc99b1..541cc9aba3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -125,7 +125,7 @@ so we need to store the shortcuts for the current editor so they can be rebound when the infinite editor closes */ - storeKeyboardShortcuts(); + unbindKeyboardShortcuts(); // set flag so we know when the editor is open in "infinie mode" editor.infiniteMode = true; @@ -676,9 +676,15 @@ * to each editor so they can be rebound when an editor closes * */ - function storeKeyboardShortcuts() { + function unbindKeyboardShortcuts() { const shortcuts = angular.copy(keyboardService.keyboardEvent); editorsKeyboardShorcuts.push(shortcuts); + + // unbind the current shortcuts because we only want to + // shortcuts from the newly opened editor working + for (let [key, value] of Object.entries(shortcuts)) { + keyboardService.unbind(key); + } } /** From 7047a07660546559088f207d2ff2f5b94b385433 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 00:03:49 +1100 Subject: [PATCH 28/80] Shows the culture for URLs if the node is invariant but it has cultural URLs (it exists underneath a variant) --- .../Routing/UrlsProviderWithDomainsTests.cs | 4 +- src/Umbraco.Web/Routing/DomainHelper.cs | 8 +- src/Umbraco.Web/Routing/UrlInfo.cs | 2 +- src/Umbraco.Web/Routing/UrlInfoComparer.cs | 66 ++++++++++++++ .../Routing/UrlProviderExtensions.cs | 89 +++++++++++++------ src/Umbraco.Web/Umbraco.Web.csproj | 1 + 6 files changed, 141 insertions(+), 29 deletions(-) create mode 100644 src/Umbraco.Web/Routing/UrlInfoComparer.cs diff --git a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs index 2f1bef3b9a..c7ce42e4bc 100644 --- a/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs +++ b/src/Umbraco.Tests/Routing/UrlsProviderWithDomainsTests.cs @@ -418,8 +418,8 @@ namespace Umbraco.Tests.Routing foreach (var x in result) Console.WriteLine(x); Assert.AreEqual(2, result.Length); - Assert.AreEqual(result[0].Text, "http://domain1a.com/en/1001-1-1/"); - Assert.AreEqual(result[1].Text, "http://domain1b.com/en/1001-1-1/"); + Assert.AreEqual(result[0].Text, "http://domain1b.com/en/1001-1-1/"); + Assert.AreEqual(result[1].Text, "http://domain1a.com/en/1001-1-1/"); } } } diff --git a/src/Umbraco.Web/Routing/DomainHelper.cs b/src/Umbraco.Web/Routing/DomainHelper.cs index b6d79e788a..9b300009d0 100644 --- a/src/Umbraco.Web/Routing/DomainHelper.cs +++ b/src/Umbraco.Web/Routing/DomainHelper.cs @@ -207,8 +207,14 @@ namespace Umbraco.Web.Routing var cultureDomains = domainsAndUris.Where(x => x.Culture.Name.InvariantEquals(culture)).ToList(); if (cultureDomains.Count > 0) return cultureDomains; + // if a culture is supplied, we *want* a url for that culture, else fail - throw new InvalidOperationException($"Could not find a domain for culture \"{culture}\"."); + //throw new InvalidOperationException($"Could not find a domain for culture \"{culture}\"."); + //fixme: Review - throwing here causes a problem because the UrlProviderExtensions.GetContentUrls iterates through + // ALL cultures even if those cultures are not assigned for use within a branch. Returning null + // here fixes that problem and the URLs resolve correctly, however i don't know if this is causing other + // residual problems. It would also suggest that below in GetByCulture we don't throw either but instead return null?? + return null; } if (defaultCulture != null) // try the defaultCulture culture diff --git a/src/Umbraco.Web/Routing/UrlInfo.cs b/src/Umbraco.Web/Routing/UrlInfo.cs index a795f1577b..ae5c4b412c 100644 --- a/src/Umbraco.Web/Routing/UrlInfo.cs +++ b/src/Umbraco.Web/Routing/UrlInfo.cs @@ -23,7 +23,7 @@ namespace Umbraco.Web.Routing /// /// Initializes a new instance of the class. /// - private UrlInfo(string text, bool isUrl, string culture) + public UrlInfo(string text, bool isUrl, string culture) { if (string.IsNullOrWhiteSpace(text)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(text)); IsUrl = isUrl; diff --git a/src/Umbraco.Web/Routing/UrlInfoComparer.cs b/src/Umbraco.Web/Routing/UrlInfoComparer.cs new file mode 100644 index 0000000000..ff41dc6bc9 --- /dev/null +++ b/src/Umbraco.Web/Routing/UrlInfoComparer.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; + +namespace Umbraco.Web.Routing +{ + /// + /// Compares + /// + public class UrlInfoComparer : IEqualityComparer + { + private readonly bool _variesByCulture; + + public UrlInfoComparer(bool variesByCulture) + { + _variesByCulture = variesByCulture; + } + + /// + /// Determines equality between + /// + /// + /// + /// + /// + /// If variesByCulture is true, then culture is compared, otherwise culture is not compared. + /// Both culture and url are compared without case sensitivity. + /// + public bool Equals(UrlInfo x, UrlInfo y) + { + if (ReferenceEquals(null, y)) return false; + if (ReferenceEquals(null, x)) return false; + if (ReferenceEquals(x, y)) return true; + + if (_variesByCulture) + { + return string.Equals(x.Culture, y.Culture, StringComparison.InvariantCultureIgnoreCase) + && x.IsUrl == y.IsUrl + && string.Equals(x.Text, y.Text, StringComparison.InvariantCultureIgnoreCase); + } + + return x.IsUrl == y.IsUrl + && string.Equals(x.Text, y.Text, StringComparison.InvariantCultureIgnoreCase); + } + + /// + /// Calculates a hash code + /// + /// + /// + /// + /// If variesByCulture is true then culture is used in the calculation, otherwise it's not + /// + public int GetHashCode(UrlInfo obj) + { + unchecked + { + var hashCode = (obj.Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.Text) : 0); + hashCode = (hashCode * 397) ^ obj.IsUrl.GetHashCode(); + if (_variesByCulture) + hashCode = (hashCode * 397) ^ (obj.Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.Culture) : 0); + return hashCode; + } + + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 3a0caeec78..71a0c294ab 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -18,7 +18,7 @@ namespace Umbraco.Web.Routing /// Use when displaying Urls. If errors occur when generating the Urls, they will show in the list. /// Contains all the Urls that we can figure out (based upon domains, etc). /// - public static IEnumerable GetContentUrls(this IContent content, + public static IEnumerable GetContentUrls(this IContent content, PublishedRouter publishedRouter, UmbracoContext umbracoContext, ILocalizationService localizationService, @@ -44,12 +44,57 @@ namespace Umbraco.Web.Routing // - the 'main' urls, which is what .Url would return, for each culture // - the 'other' urls we know (based upon domains, etc) // - // need to work on each culture. - // on invariant trees, each culture return the same thing - // but, we don't know if the tree to this content is invariant + // need to work through each installed culture. + // fixme: Why not just work with each culture assigned to domains in the branch? + // on invariant nodes, each culture returns the same url segment + // but, we don't know if the branch to this content is invariant so we need to ask + // for URLs for all cultures. var cultures = localizationService.GetAllLanguages().Select(x => x.IsoCode).ToList(); + //get all URLs for all cultures + foreach (var cultureUrl in GetContentUrlsByCulture(content, cultures, publishedRouter, umbracoContext, contentService, textService, logger)) + { + urls.Add(cultureUrl); + } + + //return the real urls first, then the messages + foreach (var urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) + { + //in some cases there will be the same URL for multiple cultures: + // * The entire branch is invariant + // * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed + + foreach (var dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant())) + yield return dUrl; + } + + // get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless. + // for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them. + foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) + if (urls.Add(otherUrl)) //avoid duplicates + yield return otherUrl; + } + + /// + /// Tries to return a for each culture for the content while detecting collisions/errors + /// + /// + /// + /// + /// + /// + /// + /// + /// + private static IEnumerable GetContentUrlsByCulture(IContent content, + IEnumerable cultures, + PublishedRouter publishedRouter, + UmbracoContext umbracoContext, + IContentService contentService, + ILocalizedTextService textService, + ILogger logger) + { foreach (var culture in cultures) { // if content is variant, and culture is not published, skip @@ -73,34 +118,26 @@ namespace Umbraco.Web.Routing { // deal with 'could not get the url' case "#": - HandleCouldNotGetUrl(content, culture, urls, contentService, textService); + yield return HandleCouldNotGetUrl(content, culture, contentService, textService); break; // deal with exceptions case "#ex": - urls.Add(UrlInfo.Message(textService.Localize("content/getUrlException"), culture)); + yield return UrlInfo.Message(textService.Localize("content/getUrlException"), culture); break; // got a url, deal with collisions, add url default: - if (!DetectCollision(content, url, urls, culture, umbracoContext, publishedRouter, textService)) // detect collisions, etc - urls.Add(UrlInfo.Url(url, culture)); + if (DetectCollision(content, url, culture, umbracoContext, publishedRouter, textService, out var urlInfo)) // detect collisions, etc + yield return urlInfo; + else + yield return UrlInfo.Url(url, culture); break; } } - - //return the real urls first, then the messages - foreach (var urlGroup in urls.GroupBy(x => x.IsUrl).OrderByDescending(x => x.Key)) - foreach (var url in urlGroup) - yield return url; - - // get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless. - // for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them. - foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) - yield return otherUrl; } - private static void HandleCouldNotGetUrl(IContent content, string culture, ICollection urls, IContentService contentService, ILocalizedTextService textService) + private static UrlInfo HandleCouldNotGetUrl(IContent content, string culture, IContentService contentService, ILocalizedTextService textService) { // document has a published version yet its url is "#" => a parent must be // unpublished, walk up the tree until we find it, and report. @@ -112,16 +149,16 @@ namespace Umbraco.Web.Routing while (parent != null && parent.Published && (!parent.ContentType.VariesByCulture() || parent.IsCulturePublished(culture))); if (parent == null) // oops, internal error - urls.Add(UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture)); + return UrlInfo.Message(textService.Localize("content/parentNotPublishedAnomaly"), culture); else if (!parent.Published) // totally not published - urls.Add(UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] { parent.Name }), culture)); + return UrlInfo.Message(textService.Localize("content/parentNotPublished", new[] {parent.Name}), culture); else // culture not published - urls.Add(UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] { parent.Name }), culture)); + return UrlInfo.Message(textService.Localize("content/parentCultureNotPublished", new[] {parent.Name}), culture); } - private static bool DetectCollision(IContent content, string url, ICollection urls, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService) + private static bool DetectCollision(IContent content, string url, string culture, UmbracoContext umbracoContext, PublishedRouter publishedRouter, ILocalizedTextService textService, out UrlInfo urlInfo) { // test for collisions on the 'main' url var uri = new Uri(url.TrimEnd('/'), UriKind.RelativeOrAbsolute); @@ -130,9 +167,11 @@ namespace Umbraco.Web.Routing var pcr = publishedRouter.CreateRequest(umbracoContext, uri); publishedRouter.TryRouteRequest(pcr); + urlInfo = null; + if (pcr.HasPublishedContent == false) { - urls.Add(UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture)); + urlInfo = UrlInfo.Message(textService.Localize("content/routeErrorCannotRoute"), culture); return true; } @@ -151,7 +190,7 @@ namespace Umbraco.Web.Routing l.Reverse(); var s = "/" + string.Join("/", l) + " (id=" + pcr.PublishedContent.Id + ")"; - urls.Add(UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture)); + urlInfo = UrlInfo.Message(textService.Localize("content/routeError", new[] { s }), culture); return true; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 699c985af3..6700c0b182 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -167,6 +167,7 @@ + From 36fd32ee9a8a9613c173b5658be67687140e9f56 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 00:04:35 +1100 Subject: [PATCH 29/80] remove unused comparer --- src/Umbraco.Web/Routing/UrlInfoComparer.cs | 66 ---------------------- src/Umbraco.Web/Umbraco.Web.csproj | 1 - 2 files changed, 67 deletions(-) delete mode 100644 src/Umbraco.Web/Routing/UrlInfoComparer.cs diff --git a/src/Umbraco.Web/Routing/UrlInfoComparer.cs b/src/Umbraco.Web/Routing/UrlInfoComparer.cs deleted file mode 100644 index ff41dc6bc9..0000000000 --- a/src/Umbraco.Web/Routing/UrlInfoComparer.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Umbraco.Web.Routing -{ - /// - /// Compares - /// - public class UrlInfoComparer : IEqualityComparer - { - private readonly bool _variesByCulture; - - public UrlInfoComparer(bool variesByCulture) - { - _variesByCulture = variesByCulture; - } - - /// - /// Determines equality between - /// - /// - /// - /// - /// - /// If variesByCulture is true, then culture is compared, otherwise culture is not compared. - /// Both culture and url are compared without case sensitivity. - /// - public bool Equals(UrlInfo x, UrlInfo y) - { - if (ReferenceEquals(null, y)) return false; - if (ReferenceEquals(null, x)) return false; - if (ReferenceEquals(x, y)) return true; - - if (_variesByCulture) - { - return string.Equals(x.Culture, y.Culture, StringComparison.InvariantCultureIgnoreCase) - && x.IsUrl == y.IsUrl - && string.Equals(x.Text, y.Text, StringComparison.InvariantCultureIgnoreCase); - } - - return x.IsUrl == y.IsUrl - && string.Equals(x.Text, y.Text, StringComparison.InvariantCultureIgnoreCase); - } - - /// - /// Calculates a hash code - /// - /// - /// - /// - /// If variesByCulture is true then culture is used in the calculation, otherwise it's not - /// - public int GetHashCode(UrlInfo obj) - { - unchecked - { - var hashCode = (obj.Text != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.Text) : 0); - hashCode = (hashCode * 397) ^ obj.IsUrl.GetHashCode(); - if (_variesByCulture) - hashCode = (hashCode * 397) ^ (obj.Culture != null ? StringComparer.InvariantCultureIgnoreCase.GetHashCode(obj.Culture) : 0); - return hashCode; - } - - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 6700c0b182..699c985af3 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -167,7 +167,6 @@ - From abb4ab6b0aba36d1ab2cacf5215afab610c86586 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 18 Dec 2018 21:56:06 +0100 Subject: [PATCH 30/80] fix open template/open doctype buttons --- .../content/umbcontentnodeinfo.directive.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 31e847f0f6..5da37ff64e 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -21,14 +21,11 @@ scope.isInfiniteMode = editorService.getNumberOfEditors() > 0 ? true : false; userService.getCurrentUser().then(function(user){ - // only allow change of media type if user has access to the settings sections - angular.forEach(user.sections, function(section){ - if(section.alias === "settings" && !scope.isInfiniteMode) { - scope.allowChangeDocumentType = true; - scope.allowChangeTemplate = true; - } - }); - }); + // only allow change of media type if user has access to the settings sections + const hasAccessToSettings = user.allowedSections.indexOf("settings") !== -1 ? true : false; + scope.allowChangeDocumentType = hasAccessToSettings; + scope.allowChangeTemplate = hasAccessToSettings; + }); var keys = [ "general_deleted", From 72d42e42281a76bbdedb5f8901c7abf88045883a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 15:58:18 +1100 Subject: [PATCH 31/80] updates the UI to show culture when the item is not variant and shows the unpublished status if the culture has no variants --- .../content/umbcontentnodeinfo.directive.js | 15 ++++++++++++++- .../components/content/umb-content-node-info.html | 8 +++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 7b56bb2bd0..4feea82d47 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -53,7 +53,8 @@ "prompt_unsavedChanges", "prompt_doctypeChangeWarning", "general_history", - "auditTrails_historyIncludingVariants" + "auditTrails_historyIncludingVariants", + "content_itemNotPublished" ]; localizationService.localizeMany(keys) @@ -65,11 +66,23 @@ labels.notCreated = data[4]; labels.unsavedChanges = data[5]; labels.doctypeChangeWarning = data[6]; + labels.notPublished = data[9]; scope.historyLabel = scope.node.variants && scope.node.variants.length === 1 ? data[7] : data[8]; setNodePublishStatus(); + if (scope.currentUrls.length === 0) { + if (scope.node.id > 0) { + //it's created but not published + scope.currentUrls.push({ text: labels.notPublished, isUrl: false }); + } + else { + //it's new + scope.currentUrls.push({ text: labels.notCreated, isUrl: false }) + } + } + }); scope.auditTrailOptions = { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index b37e3c7ba4..b1757d927f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -8,13 +8,19 @@ From 6a8c7d1a3bf1323323bcae8e0caefbe2bd56cd82 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 17:13:46 +1100 Subject: [PATCH 32/80] Writes some tests, fixes a bug, orders results, writes notes --- .../Routing/GetContentUrlsTests.cs | 136 ++++++++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + .../Routing/UrlProviderExtensions.cs | 7 +- 3 files changed, 142 insertions(+), 2 deletions(-) create mode 100644 src/Umbraco.Tests/Routing/GetContentUrlsTests.cs diff --git a/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs new file mode 100644 index 0000000000..a2a627227e --- /dev/null +++ b/src/Umbraco.Tests/Routing/GetContentUrlsTests.cs @@ -0,0 +1,136 @@ +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Moq; +using NUnit.Framework; +using Umbraco.Core.Configuration.UmbracoSettings; +using Umbraco.Core.Models; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; +using Umbraco.Web.Routing; + +namespace Umbraco.Tests.Routing +{ + [TestFixture] + public class GetContentUrlsTests : UrlRoutingTestBase + { + private IUmbracoSettingsSection _umbracoSettings; + + public override void SetUp() + { + base.SetUp(); + + //generate new mock settings and assign so we can configure in individual tests + _umbracoSettings = SettingsForTests.GenerateMockUmbracoSettings(); + SettingsForTests.ConfigureSettings(_umbracoSettings); + } + + private ILocalizedTextService GetTextService() + { + var textService = Mock.Of( + x => x.Localize("content/itemNotPublished", + It.IsAny(), + It.IsAny>()) == "content/itemNotPublished"); + return textService; + } + + private ILocalizationService GetLangService(params string[] isoCodes) + { + var allLangs = isoCodes + .Select(CultureInfo.GetCultureInfo) + .Select(culture => new Language(culture.Name) + { + CultureName = culture.DisplayName, + IsDefault = true, + IsMandatory = true + }).ToArray(); + + var langService = Mock.Of(x => x.GetAllLanguages() == allLangs); + return langService; + } + + [Test] + public void Content_Not_Published() + { + var contentType = MockedContentTypes.CreateBasicContentType(); + var content = MockedContent.CreateBasicContent(contentType); + content.Id = 1046; //fixme: we are using this ID only because it's built into the test XML published cache + content.Path = "-1,1046"; + + var umbContext = GetUmbracoContext("http://localhost:8000"); + var publishedRouter = CreatePublishedRouter(Container, + contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(Logger) })); + var urls = content.GetContentUrls(publishedRouter, + umbContext, + GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, + Logger).ToList(); + + Assert.AreEqual(1, urls.Count); + Assert.AreEqual("content/itemNotPublished", urls[0].Text); + Assert.IsFalse(urls[0].IsUrl); + } + + [Test] + public void Invariant_Root_Content_Published_No_Domains() + { + var contentType = MockedContentTypes.CreateBasicContentType(); + var content = MockedContent.CreateBasicContent(contentType); + content.Id = 1046; //fixme: we are using this ID only because it's built into the test XML published cache + content.Path = "-1,1046"; + content.Published = true; + + var umbContext = GetUmbracoContext("http://localhost:8000", + urlProviders: new []{ new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper()) }); + var publishedRouter = CreatePublishedRouter(Container, + contentFinders:new ContentFinderCollection(new[]{new ContentFinderByUrl(Logger) })); + var urls = content.GetContentUrls(publishedRouter, + umbContext, + GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, + Logger).ToList(); + + Assert.AreEqual(1, urls.Count); + Assert.AreEqual("/home/", urls[0].Text); + Assert.AreEqual("en-US", urls[0].Culture); + Assert.IsTrue(urls[0].IsUrl); + } + + [Test] + public void Invariant_Child_Content_Published_No_Domains() + { + var contentType = MockedContentTypes.CreateBasicContentType(); + var parent = MockedContent.CreateBasicContent(contentType); + parent.Id = 1046; //fixme: we are using this ID only because it's built into the test XML published cache + parent.Name = "home"; + parent.Path = "-1,1046"; + parent.Published = true; + var child = MockedContent.CreateBasicContent(contentType); + child.Name = "sub1"; + child.Id = 1173; //fixme: we are using this ID only because it's built into the test XML published cache + child.Path = "-1,1046,1173"; + child.Published = true; + + var umbContext = GetUmbracoContext("http://localhost:8000", + urlProviders: new[] { new DefaultUrlProvider(_umbracoSettings.RequestHandler, Logger, TestObjects.GetGlobalSettings(), new SiteDomainHelper()) }); + var publishedRouter = CreatePublishedRouter(Container, + contentFinders: new ContentFinderCollection(new[] { new ContentFinderByUrl(Logger) })); + var urls = child.GetContentUrls(publishedRouter, + umbContext, + GetLangService("en-US", "fr-FR"), GetTextService(), ServiceContext.ContentService, + Logger).ToList(); + + Assert.AreEqual(1, urls.Count); + Assert.AreEqual("/home/sub1/", urls[0].Text); + Assert.AreEqual("en-US", urls[0].Culture); + Assert.IsTrue(urls[0].IsUrl); + } + + //TODO: We need a lot of tests here, the above was just to get started with being able to unit test this method + // * variant URLs without domains assigned, what happens? + // * variant URLs with domains assigned, but also having more languages installed than there are domains/cultures assigned + // * variant URLs with an ancestor culture unpublished + // * invariant URLs with ancestors as variants + // * ... probably a lot more + + } +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index baeb667fcc..ed2324e02c 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -132,6 +132,7 @@ + diff --git a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs index 71a0c294ab..de9314704b 100644 --- a/src/Umbraco.Web/Routing/UrlProviderExtensions.cs +++ b/src/Umbraco.Web/Routing/UrlProviderExtensions.cs @@ -35,7 +35,10 @@ namespace Umbraco.Web.Routing if (logger == null) throw new ArgumentNullException(nameof(logger)); if (content.Published == false) + { yield return UrlInfo.Message(textService.Localize("content/itemNotPublished")); + yield break; + } var urls = new HashSet(); @@ -65,13 +68,13 @@ namespace Umbraco.Web.Routing // * The entire branch is invariant // * If there are less domain/cultures assigned to the branch than the number of cultures/languages installed - foreach (var dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant())) + foreach (var dUrl in urlGroup.DistinctBy(x => x.Text.ToUpperInvariant()).OrderBy(x => x.Text).ThenBy(x => x.Culture)) yield return dUrl; } // get the 'other' urls - ie not what you'd get with GetUrl() but urls that would route to the document, nevertheless. // for these 'other' urls, we don't check whether they are routable, collide, anything - we just report them. - foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id)) + foreach (var otherUrl in umbracoContext.UrlProvider.GetOtherUrls(content.Id).OrderBy(x => x.Text).ThenBy(x => x.Culture)) if (urls.Add(otherUrl)) //avoid duplicates yield return otherUrl; } From 0ad66c488bede675355bf7d5984cecb3b1d36728 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 22:06:04 +1100 Subject: [PATCH 33/80] Long live Operathor --- .../Editors/TemplateQueryController.cs | 32 +++++++++---------- .../{Operathor.cs => Operator.cs} | 2 +- .../{OperathorTerm.cs => OperatorTerm.cs} | 12 +++---- .../Models/TemplateQuery/QueryCondition.cs | 20 ++++++------ src/Umbraco.Web/Umbraco.Web.csproj | 4 +-- 5 files changed, 35 insertions(+), 35 deletions(-) rename src/Umbraco.Web/Models/TemplateQuery/{Operathor.cs => Operator.cs} (90%) rename src/Umbraco.Web/Models/TemplateQuery/{OperathorTerm.cs => OperatorTerm.cs} (56%) diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 0d0438138a..3122390047 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -18,26 +18,26 @@ namespace Umbraco.Web.Editors [JsonCamelCaseFormatter] public class TemplateQueryController : UmbracoAuthorizedJsonController { - private IEnumerable Terms + private IEnumerable Terms { get { - return new List() + return new List() { - new OperathorTerm(Services.TextService.Localize("template/is"), Operathor.Equals, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/isNot"), Operathor.NotEquals, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/before"), Operathor.LessThan, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/beforeIncDate"), Operathor.LessThanEqualTo, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/after"), Operathor.GreaterThan, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/afterIncDate"), Operathor.GreaterThanEqualTo, new [] {"datetime"}), - new OperathorTerm(Services.TextService.Localize("template/equals"), Operathor.Equals, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/doesNotEqual"), Operathor.NotEquals, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/contains"), Operathor.Contains, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/doesNotContain"), Operathor.NotContains, new [] {"string"}), - new OperathorTerm(Services.TextService.Localize("template/greaterThan"), Operathor.GreaterThan, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/greaterThanEqual"), Operathor.GreaterThanEqualTo, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/lessThan"), Operathor.LessThan, new [] {"int"}), - new OperathorTerm(Services.TextService.Localize("template/lessThanEqual"), Operathor.LessThanEqualTo, new [] {"int"}) + new OperatorTerm(Services.TextService.Localize("template/is"), Operator.Equals, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/isNot"), Operator.NotEquals, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/before"), Operator.LessThan, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/beforeIncDate"), Operator.LessThanEqualTo, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/after"), Operator.GreaterThan, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/afterIncDate"), Operator.GreaterThanEqualTo, new [] {"datetime"}), + new OperatorTerm(Services.TextService.Localize("template/equals"), Operator.Equals, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/doesNotEqual"), Operator.NotEquals, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/contains"), Operator.Contains, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/doesNotContain"), Operator.NotContains, new [] {"string"}), + new OperatorTerm(Services.TextService.Localize("template/greaterThan"), Operator.GreaterThan, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/greaterThanEqual"), Operator.GreaterThanEqualTo, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/lessThan"), Operator.LessThan, new [] {"int"}), + new OperatorTerm(Services.TextService.Localize("template/lessThanEqual"), Operator.LessThanEqualTo, new [] {"int"}) }; } } diff --git a/src/Umbraco.Web/Models/TemplateQuery/Operathor.cs b/src/Umbraco.Web/Models/TemplateQuery/Operator.cs similarity index 90% rename from src/Umbraco.Web/Models/TemplateQuery/Operathor.cs rename to src/Umbraco.Web/Models/TemplateQuery/Operator.cs index 561caec362..135c43507e 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/Operathor.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/Operator.cs @@ -1,6 +1,6 @@ namespace Umbraco.Web.Models.TemplateQuery { - public enum Operathor + public enum Operator { Equals = 1, NotEquals = 2, diff --git a/src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs b/src/Umbraco.Web/Models/TemplateQuery/OperatorTerm.cs similarity index 56% rename from src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs rename to src/Umbraco.Web/Models/TemplateQuery/OperatorTerm.cs index c14e1854aa..086f0ff818 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/OperathorTerm.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/OperatorTerm.cs @@ -2,24 +2,24 @@ namespace Umbraco.Web.Models.TemplateQuery { - public class OperathorTerm + public class OperatorTerm { - public OperathorTerm() + public OperatorTerm() { Name = "is"; - Operathor = Operathor.Equals; + Operator = Operator.Equals; AppliesTo = new [] { "string" }; } - public OperathorTerm(string name, Operathor operathor, IEnumerable appliesTo) + public OperatorTerm(string name, Operator @operator, IEnumerable appliesTo) { Name = name; - Operathor = operathor; + Operator = @operator; AppliesTo = appliesTo; } public string Name { get; set; } - public Operathor Operathor { get; set; } + public Operator Operator { get; set; } public IEnumerable AppliesTo { get; set; } } } diff --git a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs index 8ba943756f..9c5f2c80c0 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs @@ -4,7 +4,7 @@ { public PropertyModel Property { get; set; } - public OperathorTerm Term { get; set; } + public OperatorTerm Term { get; set; } public string ConstraintValue { get; set; } } @@ -53,30 +53,30 @@ } - switch (condition.Term.Operathor) + switch (condition.Term.Operator) { - case Operathor.Equals: + case Operator.Equals: operand = " == "; break; - case Operathor.NotEquals: + case Operator.NotEquals: operand = " != "; break; - case Operathor.GreaterThan: + case Operator.GreaterThan: operand = " > "; break; - case Operathor.GreaterThanEqualTo: + case Operator.GreaterThanEqualTo: operand = " >= "; break; - case Operathor.LessThan: + case Operator.LessThan: operand = " < "; break; - case Operathor.LessThanEqualTo: + case Operator.LessThanEqualTo: operand = " <= "; break; - case Operathor.Contains: + case Operator.Contains: value = string.Format("{0}{1}.Contains({2})", prefix, condition.Property.Alias, constraintValue); break; - case Operathor.NotContains: + case Operator.NotContains: value = string.Format("!{0}{1}.Contains({2})", prefix, condition.Property.Alias, constraintValue); break; default : diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 699c985af3..c7e8b0d109 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -685,8 +685,8 @@ - - + + From f72474822574554786dbd56994bb648c4d5dd538 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 19 Dec 2018 23:27:47 +1100 Subject: [PATCH 34/80] No more IUserService.SavePassword - passwords are managed with Identity. --- src/Umbraco.Core/Services/IUserService.cs | 10 -------- .../Services/Implement/UserService.cs | 24 ------------------- .../Editors/TemplateQueryController.cs | 1 + 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/src/Umbraco.Core/Services/IUserService.cs b/src/Umbraco.Core/Services/IUserService.cs index cb2d90aa63..a926ce32aa 100644 --- a/src/Umbraco.Core/Services/IUserService.cs +++ b/src/Umbraco.Core/Services/IUserService.cs @@ -90,16 +90,6 @@ namespace Umbraco.Core.Services string[] userGroups = null, string filter = null); - /// - /// This is simply a helper method which essentially just wraps the MembershipProvider's ChangePassword method - /// - /// - /// This method exists so that Umbraco developers can use one entry point to create/update users if they choose to. - /// - /// The user to save the password for - /// The password to save - void SavePassword(IUser user, string password); - /// /// Deletes or disables a User /// diff --git a/src/Umbraco.Core/Services/Implement/UserService.cs b/src/Umbraco.Core/Services/Implement/UserService.cs index 44358caa84..188c6feb04 100644 --- a/src/Umbraco.Core/Services/Implement/UserService.cs +++ b/src/Umbraco.Core/Services/Implement/UserService.cs @@ -227,30 +227,6 @@ namespace Umbraco.Core.Services.Implement Save(membershipUser); } - [Obsolete("ASP.NET Identity APIs like the BackOfficeUserManager should be used to manage passwords, this will not work with correct security practices because you would need the existing password")] - [EditorBrowsable(EditorBrowsableState.Never)] - public void SavePassword(IUser user, string password) - { - if (user == null) throw new ArgumentNullException(nameof(user)); - - var provider = MembershipProviderExtensions.GetUsersMembershipProvider(); - - if (provider.IsUmbracoMembershipProvider() == false) - throw new NotSupportedException("When using a non-Umbraco membership provider you must change the user password by using the MembershipProvider.ChangePassword method"); - - provider.ChangePassword(user.Username, "", password); - - //go re-fetch the member and update the properties that may have changed - var result = GetByUsername(user.Username); - if (result != null) - { - //should never be null but it could have been deleted by another thread. - user.RawPasswordValue = result.RawPasswordValue; - user.LastPasswordChangeDate = result.LastPasswordChangeDate; - user.UpdateDate = result.UpdateDate; - } - } - /// /// Deletes or disables a User /// diff --git a/src/Umbraco.Web/Editors/TemplateQueryController.cs b/src/Umbraco.Web/Editors/TemplateQueryController.cs index 3122390047..00e124cb29 100644 --- a/src/Umbraco.Web/Editors/TemplateQueryController.cs +++ b/src/Umbraco.Web/Editors/TemplateQueryController.cs @@ -67,6 +67,7 @@ namespace Umbraco.Web.Editors sb.Append("Model.Root()"); + //fixme: This timer thing is not correct, it's definitely not timing the resulting query, the timer really isn't important and might as well be removed var timer = new Stopwatch(); timer.Start(); From cf56dbd34e43628c9b1bbc15489e048e54c96800 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 19 Dec 2018 13:43:58 +0100 Subject: [PATCH 35/80] add more docs for infinite editing - how to create a custom infinite editor --- .../src/common/services/editor.service.js | 114 +++++++++++++++--- 1 file changed, 99 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 541cc9aba3..0d6432b01f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -5,29 +5,29 @@ * @description * Added in Umbraco 8.0. Application-wide service for handling infinite editing. * + * * + * +

Open a build-in infinite editor (media picker)

Markup example

-    
- - - +
+

Controller example

     (function () {
-
         "use strict";
 
-        function Controller() {
+        function MediaPickerController(editorService) {
 
             var vm = this;
 
-            vm.open = open;
+            vm.openMediaPicker = openMediaPicker;
 
-            function open() {
+            function openMediaPicker() {
                 var mediaPickerOptions = {
                     multiPicker: true,
                     submit: function(model) {
@@ -36,22 +36,29 @@
                     close: function() {
                         editorService.close();
                     }
-                }
+                };
                 editorService.mediaPicker(mediaPickerOptions);
             };
         }
 
-        angular.module("umbraco").controller("My.Controller", Controller);
+        angular.module("umbraco").controller("My.MediaPickerController", MediaPickerController);
     })();
 
-

Custom view example

+

Building a custom infinite editor

+

Open the custom infinite editor (Markup)

+
+    
+ +
+
+ +

Open the custom infinite editor (Controller)

     (function () {
-
         "use strict";
 
-        function Controller() {
+        function Controller(editorService) {
 
             var vm = this;
 
@@ -59,14 +66,15 @@
 
             function open() {
                 var options = {
-                    view: "path/to/view.html"
+                    title: "My custom infinite editor",
+                    view: "path/to/view.html",
                     submit: function(model) {
                         editorService.close();
                     },
                     close: function() {
                         editorService.close();
                     }
-                }
+                };
                 editorService.open(options);
             };
         }
@@ -74,7 +82,83 @@
         angular.module("umbraco").controller("My.Controller", Controller);
     })();
 
+ +

The custom infinite editor view

+When building a custom infinite editor view you can use the same components as a normal editor ({@link umbraco.directives.directive:umbEditorView umbEditorView}). +
+    
+ + + + + + + + + + {{model | json}} + + + + + + + + + + + + + + + +
+
+ +

The custom infinite editor controller

+
+    (function () {
+        "use strict";
+
+        function InfiniteEditorController($scope) {
+
+            var vm = this;
+
+            vm.submit = submit;
+            vm.close = close;
+
+            function submit() {
+                if($scope.model.submit) {
+                    $scope.model.submit($scope.model);
+                }
+            }
+
+            function close() {
+                if($scope.model.close) {
+                    $scope.model.close();
+                }
+            }
+
+        }
+
+        angular.module("umbraco").controller("My.InfiniteEditorController", InfiniteEditorController);
+    })();
+
*/ + (function () { "use strict"; From 150ab1366863e1fd7456346c67c9c8dd4d1c433c Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Thu, 15 Nov 2018 20:13:52 +0000 Subject: [PATCH 36/80] 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 b81f2512cb8a5c91210b4098848364549b2a142e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 19 Dec 2018 15:01:18 +0100 Subject: [PATCH 37/80] Fix the disappearing preview/save/publish buttons in listviews (#3906) --- .../components/content/edit.controller.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index f1e2150579..8564cf2c43 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -24,16 +24,19 @@ $scope.page.hideActionsMenu = infiniteMode ? true : false; $scope.page.hideChangeVariant = infiniteMode ? true : false; $scope.allowOpen = true; + $scope.app = null; function init(content) { - - // set first app to active - content.apps[0].active = true; + if (!$scope.app) { + // set first app to active + content.apps[0].active = true; + $scope.app = content.apps[0]; + } if (infiniteMode) { createInfiniteModeButtons(content); } else { - createButtons(content, content.apps[0]); + createButtons(content); } editorState.set($scope.content); @@ -146,11 +149,11 @@ * @param {any} content the content node * @param {any} app the active content app */ - function createButtons(content, app) { + function createButtons(content) { // only create the save/publish/preview buttons if the // content app is "Conent" - if (app && app.alias !== "umbContent" && app.alias !== "umbInfo") { + if ($scope.app && $scope.app.alias !== "umbContent" && $scope.app.alias !== "umbInfo") { $scope.defaultButton = null; $scope.subButtons = null; $scope.page.showSaveButton = false; @@ -899,7 +902,8 @@ * @param {any} app */ $scope.appChanged = function (app) { - createButtons($scope.content, app); + $scope.app = app; + createButtons($scope.content); }; // methods for infinite editing From 08dc5a40381a6a1e6b27bb22844a196ba294a08b Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 3 Dec 2018 13:20:50 +0100 Subject: [PATCH 38/80] 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 39/80] 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 40/80] 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 7b804e27ad2a6c7d90f6e78a2c7d7fdfba0972d7 Mon Sep 17 00:00:00 2001 From: Thomas Morris Date: Fri, 14 Dec 2018 12:20:04 +0000 Subject: [PATCH 41/80] Removes umbraco.aspx --- .../Configurations/GlobalSettingsTests.cs | 3 +- .../CoreThings/UriExtensionsTests.cs | 6 +-- .../Routing/UmbracoModuleTests.cs | 3 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 8 ---- src/Umbraco.Web.UI/Umbraco/config/lang/de.xml | 4 +- src/Umbraco.Web.UI/Umbraco/umbraco.aspx | 2 - src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs | 19 --------- .../Umbraco/umbraco.aspx.designer.cs | 15 ------- src/Umbraco.Web.UI/default.aspx | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 3 -- .../umbraco/Default.aspx.cs | 39 ------------------- 11 files changed, 7 insertions(+), 97 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/umbraco.aspx delete mode 100644 src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs delete mode 100644 src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs diff --git a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs index ebba8bc1cc..34fb2add8b 100644 --- a/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs +++ b/src/Umbraco.Tests/Configurations/GlobalSettingsTests.cs @@ -52,8 +52,7 @@ namespace Umbraco.Tests.Configurations SystemDirectories.Root = rootPath; Assert.AreEqual(outcome, UmbracoConfig.For.GlobalSettings().GetUmbracoMvcArea()); } - - [TestCase("/umbraco/umbraco.aspx")] + [TestCase("/umbraco/editContent.aspx")] [TestCase("/install/default.aspx")] [TestCase("/install/")] diff --git a/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs b/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs index 7f00bed123..f253d44973 100644 --- a/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs +++ b/src/Umbraco.Tests/CoreThings/UriExtensionsTests.cs @@ -32,8 +32,7 @@ namespace Umbraco.Tests.CoreThings [TestCase("http://www.domain.com/umbraco/test/test.js", "", true)] [TestCase("http://www.domain.com/umbrac", "", false)] [TestCase("http://www.domain.com/test", "", false)] - [TestCase("http://www.domain.com/test/umbraco", "", false)] - [TestCase("http://www.domain.com/test/umbraco.aspx", "", false)] + [TestCase("http://www.domain.com/test/umbraco", "", false)] [TestCase("http://www.domain.com/Umbraco/restServices/blah", "", true)] [TestCase("http://www.domain.com/Umbraco/Backoffice/blah", "", true)] [TestCase("http://www.domain.com/Umbraco/anything", "", true)] @@ -62,8 +61,7 @@ namespace Umbraco.Tests.CoreThings [TestCase("http://www.domain.com/install/test/test.js", true)] [TestCase("http://www.domain.com/instal", false)] [TestCase("http://www.domain.com/umbraco", false)] - [TestCase("http://www.domain.com/umbraco/umbraco", false)] - [TestCase("http://www.domain.com/test/umbraco.aspx", false)] + [TestCase("http://www.domain.com/umbraco/umbraco", false)] public void Is_Installer_Request(string input, bool expected) { var source = new Uri(input); diff --git a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs index 5f42c8d3ae..69533c3c77 100644 --- a/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs +++ b/src/Umbraco.Tests/Routing/UmbracoModuleTests.cs @@ -62,8 +62,7 @@ namespace Umbraco.Tests.Routing // do not test for /base here as it's handled before EnsureUmbracoRoutablePage is called [TestCase("/umbraco_client/Tree/treeIcons.css", false)] - [TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", false)] - [TestCase("/umbraco/umbraco.aspx", false)] + [TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", false)] [TestCase("/umbraco/editContent.aspx", false)] [TestCase("/install/default.aspx", false)] [TestCase("/install/?installStep=license", false)] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 5751e9155c..b4a020deea 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -218,14 +218,6 @@ - - - umbraco.aspx - ASPXCodeBehind - - - umbraco.aspx - diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml index b2296b1ccf..0edabebb76 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/de.xml @@ -881,7 +881,7 @@ das Dokument '%1%' wurde von '%2%' zur Übersetzung in '%5%' freigegeben. Zum Bearbeiten verwenden Sie bitte diesen Link: http://%3%/translation/details.aspx?id=%4%. -Sie können sich auch alle anstehenden Übersetzungen gesammelt im Umbraco-Verwaltungsbereich anzeigen lassen: http://%3%/Umbraco.aspx +Sie können sich auch alle anstehenden Übersetzungen gesammelt im Umbraco-Verwaltungsbereich anzeigen lassen: http://%3%/umbraco Einen schönen Tag wünscht Ihr freundlicher Umbraco-Robot @@ -977,4 +977,4 @@ Ihr freundlicher Umbraco-Robot Ihr Verlauf Sitzung läuft ab in - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx b/src/Umbraco.Web.UI/Umbraco/umbraco.aspx deleted file mode 100644 index 6e70513afd..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx +++ /dev/null @@ -1,2 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="umbraco.aspx.cs" Inherits="Umbraco.Web.UI.Umbraco.umbraco" %> - diff --git a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs b/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs deleted file mode 100644 index b70378bb01..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Web; -using System.Web.UI; -using System.Web.UI.WebControls; -using Umbraco.Core.Configuration; - -namespace Umbraco.Web.UI.Umbraco -{ - public partial class umbraco : System.Web.UI.Page - { - protected void Page_Load(object sender, EventArgs e) - { - Response.Status = "301 Moved Permanently"; - Response.AddHeader("Location", UmbracoConfig.For.GlobalSettings().Path); - } - } -} diff --git a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs b/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs deleted file mode 100644 index c9a577fb34..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/umbraco.aspx.designer.cs +++ /dev/null @@ -1,15 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Umbraco.Web.UI.Umbraco { - - - public partial class umbraco { - } -} diff --git a/src/Umbraco.Web.UI/default.aspx b/src/Umbraco.Web.UI/default.aspx index 75b0e5d28c..a41d1eccf7 100644 --- a/src/Umbraco.Web.UI/default.aspx +++ b/src/Umbraco.Web.UI/default.aspx @@ -1,2 +1,2 @@ -<%@ Page language="c#" Codebehind="default.aspx.cs" AutoEventWireup="True" Inherits="umbraco.UmbracoDefault" trace="true" validateRequest="false" %> +<%@ Page language="c#" AutoEventWireup="True" Inherits="umbraco.UmbracoDefault" trace="true" validateRequest="false" %> diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c7e8b0d109..ea3ff32220 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1166,9 +1166,6 @@ ASPXCodeBehind - - ASPXCodeBehind - ASPXCodeBehind diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs deleted file mode 100644 index f1a05c1185..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/Default.aspx.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Web; -using System.Web.Mvc; -using System.Web.SessionState; -using System.Web.UI; -using System.Web.UI.WebControls; -using System.Web.UI.HtmlControls; - -namespace umbraco -{ - /// - /// Summary description for _Default. - /// - public partial class _Default : System.Web.UI.Page - { - protected void Page_Load(object sender, System.EventArgs e) - { - //var mvcHandler = new MvcHandler() - //Server.TransferRequest(); - //Server.Transfer("~/Umbraco/Default"); - //Server.Transfer("umbraco.aspx"); - // Put user code to initialize the page here - } - - /// - /// Form1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.HtmlControls.HtmlForm Form1; - - } -} From f3e8b70b8cf816a8030da0913f1af05500ddf86a Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 30 Nov 2018 15:07:48 +0100 Subject: [PATCH 42/80] 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 }}
-