From 0abe685d7f9d5c37315ba830fe00fb1b4ee0ccf2 Mon Sep 17 00:00:00 2001 From: elitsa Date: Mon, 3 Dec 2018 09:00:44 +0100 Subject: [PATCH 01/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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/45] 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 c8b3f11498506f5072c71df96e7b820bf93eddf4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 13 Dec 2018 15:04:18 +1100 Subject: [PATCH 13/45] latest examine, tweaks to UmbracoFieldDefinitionCollection, adds some test code for testing --- .../{IUmbracoIndexer.cs => IUmbracoIndex.cs} | 2 +- src/Umbraco.Examine/Umbraco.Examine.csproj | 4 +- src/Umbraco.Examine/UmbracoContentIndex.cs | 62 ++++++++++++------- src/Umbraco.Examine/UmbracoExamineIndex.cs | 4 +- src/Umbraco.Examine/UmbracoMemberIndex.cs | 4 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- .../UmbracoExamine/IndexInitializer.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- .../Editors/ExamineManagementController.cs | 2 +- src/Umbraco.Web/PublishedContentQuery.cs | 4 +- src/Umbraco.Web/Search/ExamineComponent.cs | 22 +++++-- .../Search/GenericIndexDiagnostics.cs | 2 +- .../Search/UmbracoIndexesCreator.cs | 9 +-- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 14 files changed, 74 insertions(+), 49 deletions(-) rename src/Umbraco.Examine/{IUmbracoIndexer.cs => IUmbracoIndex.cs} (94%) diff --git a/src/Umbraco.Examine/IUmbracoIndexer.cs b/src/Umbraco.Examine/IUmbracoIndex.cs similarity index 94% rename from src/Umbraco.Examine/IUmbracoIndexer.cs rename to src/Umbraco.Examine/IUmbracoIndex.cs index 00f772b1e2..e70652a342 100644 --- a/src/Umbraco.Examine/IUmbracoIndexer.cs +++ b/src/Umbraco.Examine/IUmbracoIndex.cs @@ -5,7 +5,7 @@ namespace Umbraco.Examine /// /// A Marker interface for defining an Umbraco indexer /// - public interface IUmbracoIndexer : IIndex + public interface IUmbracoIndex : IIndex { /// /// When set to true Umbraco will keep the index in sync with Umbraco data automatically diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 66b1f09068..3c2f5a8ea6 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -48,7 +48,7 @@ - + @@ -72,7 +72,7 @@ - + diff --git a/src/Umbraco.Examine/UmbracoContentIndex.cs b/src/Umbraco.Examine/UmbracoContentIndex.cs index 910941ea7b..ac9683f3f2 100644 --- a/src/Umbraco.Examine/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine/UmbracoContentIndex.cs @@ -18,6 +18,45 @@ using Examine.LuceneEngine; namespace Umbraco.Examine { + /// + /// Custom allowing dynamic creation of + /// + public class UmbracoFieldDefinitionCollection : FieldDefinitionCollection + { + + public UmbracoFieldDefinitionCollection() + : base(UmbracoExamineIndex.UmbracoIndexFieldDefinitions) + { + } + + ///// + ///// Overridden to dynamically add field definitions for culture variations + ///// + ///// + ///// + ///// + //public override bool TryGetValue(string fieldName, out FieldDefinition fieldDefinition) + //{ + // var result = base.TryGetValue(fieldName, out fieldDefinition); + // if (result) return true; + + // //if the fieldName is not suffixed with _iso-Code + // var underscoreIndex = fieldName.LastIndexOf('_'); + // if (underscoreIndex == -1) return false; + + + + // var isoCode = fieldName.Substring(underscoreIndex); + // if (isoCode.Length < 6) return false; //invalid isoCode + + // var hyphenIndex = isoCode.IndexOf('-'); + // if (hyphenIndex != 3) return false; //invalid isoCode + + // //we'll assume this is a valid isoCode + + //} + } + /// /// An indexer for Umbraco content and media /// @@ -52,7 +91,7 @@ namespace Umbraco.Examine /// public UmbracoContentIndex( string name, - IEnumerable fieldDefinitions, + FieldDefinitionCollection fieldDefinitions, Directory luceneDirectory, Analyzer defaultAnalyzer, ProfilingLogger profilingLogger, @@ -214,27 +253,6 @@ namespace Umbraco.Examine base.PerformDeleteFromIndex(nodeId, onComplete); } - - /// - /// Overridden to ensure that the variant system fields have the right value types - /// - /// - /// - /// - protected override FieldValueTypeCollection CreateFieldValueTypes(IReadOnlyDictionary> indexValueTypesFactory = null) - { - //fixme: languages are dynamic so although this will work on startup it wont work when languages are edited - foreach(var lang in LanguageService.GetAllLanguages()) - { - foreach (var field in UmbracoIndexFieldDefinitions) - { - var def = new FieldDefinition($"{field.Name}_{lang.IsoCode.ToLowerInvariant()}", field.Type); - FieldDefinitionCollection.TryAdd(def.Name, def); - } - } - - return base.CreateFieldValueTypes(indexValueTypesFactory); - } } } diff --git a/src/Umbraco.Examine/UmbracoExamineIndex.cs b/src/Umbraco.Examine/UmbracoExamineIndex.cs index 15f1a75955..8d84e417d4 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndex.cs @@ -23,7 +23,7 @@ namespace Umbraco.Examine /// An abstract provider containing the basic functionality to be able to query against /// Umbraco data. /// - public abstract class UmbracoExamineIndex : LuceneIndex, IUmbracoIndexer, IIndexDiagnostics + public abstract class UmbracoExamineIndex : LuceneIndex, IUmbracoIndex, IIndexDiagnostics { // note // wrapping all operations that end up calling base.SafelyProcessQueueItems in a safe call @@ -67,7 +67,7 @@ namespace Umbraco.Examine /// protected UmbracoExamineIndex( string name, - IEnumerable fieldDefinitions, + FieldDefinitionCollection fieldDefinitions, Directory luceneDirectory, Analyzer defaultAnalyzer, ProfilingLogger profilingLogger, diff --git a/src/Umbraco.Examine/UmbracoMemberIndex.cs b/src/Umbraco.Examine/UmbracoMemberIndex.cs index 28b46f72dd..43962855c9 100644 --- a/src/Umbraco.Examine/UmbracoMemberIndex.cs +++ b/src/Umbraco.Examine/UmbracoMemberIndex.cs @@ -42,8 +42,8 @@ namespace Umbraco.Examine /// /// public UmbracoMemberIndex( - string name, - IEnumerable fieldDefinitions, + string name, + FieldDefinitionCollection fieldDefinitions, Directory luceneDirectory, Analyzer analyzer, ProfilingLogger profilingLogger, diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 513701bf6a..b31a78a08f 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -77,7 +77,7 @@ - + 1.8.9 diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs index cbd335a6c4..4435a5e829 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexInitializer.cs @@ -164,7 +164,7 @@ namespace Umbraco.Tests.UmbracoExamine var i = new UmbracoContentIndex( "testIndexer", - UmbracoExamineIndex.UmbracoIndexFieldDefinitions, + new UmbracoFieldDefinitionCollection(), luceneDir, analyzer, profilingLogger, diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 5751e9155c..60e1324969 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -88,7 +88,7 @@ - + diff --git a/src/Umbraco.Web/Editors/ExamineManagementController.cs b/src/Umbraco.Web/Editors/ExamineManagementController.cs index 2f96ee3d45..6667510b49 100644 --- a/src/Umbraco.Web/Editors/ExamineManagementController.cs +++ b/src/Umbraco.Web/Editors/ExamineManagementController.cs @@ -75,7 +75,7 @@ namespace Umbraco.Web.Editors var results = Examine.ExamineExtensions.TryParseLuceneQuery(query) ? searcher.Search(searcher.CreateCriteria().RawQuery(query), maxResults: pageSize * (pageIndex + 1)) - : searcher.Search(query, true, maxResults: pageSize * (pageIndex + 1)); + : searcher.Search(query, maxResults: pageSize * (pageIndex + 1)); var pagedResults = results.Skip(pageIndex * pageSize); diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 67b54330c5..f27745055c 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -243,8 +243,8 @@ namespace Umbraco.Web var searcher = index.GetSearcher(); var results = skip == 0 && take == 0 - ? searcher.Search(term, true) - : searcher.Search(term, true, maxResults: skip + take); + ? searcher.Search(term) + : searcher.Search(term, maxResults: skip + take); totalRecords = results.TotalItemCount; return results.ToPublishedSearchResults(_contentCache); diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 65264d0308..3061a668ca 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -149,7 +149,7 @@ namespace Umbraco.Web.Search profilingLogger.Logger.Debug("Examine shutdown registered with MainDom"); - var registeredIndexers = examineManager.Indexes.OfType().Count(x => x.EnableDefaultEventHandler); + var registeredIndexers = examineManager.Indexes.OfType().Count(x => x.EnableDefaultEventHandler); profilingLogger.Logger.Info("Adding examine event handlers for {RegisteredIndexers} index providers.", registeredIndexers); @@ -166,9 +166,19 @@ namespace Umbraco.Web.Search EnsureUnlocked(profilingLogger.Logger, examineManager); + //TODO: Instead of waiting 5000 ms, we could add an event handler on to fulfilling the first request, then start? RebuildIndexes(indexRebuilder, profilingLogger.Logger, true, 5000); } + private void TestExtending(IExamineManager examineManager) + { + if (examineManager.TryGetIndex("Test", out var index) && index is LuceneIndex umbIndex) + { + umbIndex.FieldValueTypeCollection.ValueTypeFactories.TryAdd() + umbIndex.FieldDefinitionCollection.TryAdd("productName_es-es", new FieldDefinition("productName_es-es", "")); + } + } + /// /// Called to rebuild empty indexes on startup @@ -496,7 +506,7 @@ namespace Umbraco.Web.Search //Delete all content of this content/media/member type that is in any content indexer by looking up matched examine docs foreach (var id in ci.Value.removedIds) { - foreach (var index in _examineManager.Indexes.OfType()) + foreach (var index in _examineManager.Indexes.OfType()) { var searcher = index.GetSearcher(); @@ -702,7 +712,7 @@ namespace Umbraco.Web.Search public static void Execute(ExamineComponent examineComponent, IContent content, bool isPublished) { - foreach (var index in examineComponent._examineManager.Indexes.OfType() + foreach (var index in examineComponent._examineManager.Indexes.OfType() //filter the indexers .Where(x => isPublished || !x.PublishedValuesOnly) .Where(x => x.EnableDefaultEventHandler)) @@ -739,7 +749,7 @@ namespace Umbraco.Web.Search { var valueSet = examineComponent._mediaValueSetBuilder.GetValueSets(media).ToList(); - foreach (var index in examineComponent._examineManager.Indexes.OfType() + foreach (var index in examineComponent._examineManager.Indexes.OfType() //filter the indexers .Where(x => isPublished || !x.PublishedValuesOnly) .Where(x => x.EnableDefaultEventHandler)) @@ -768,7 +778,7 @@ namespace Umbraco.Web.Search public static void Execute(ExamineComponent examineComponent, IMember member) { var valueSet = examineComponent._memberValueSetBuilder.GetValueSets(member).ToList(); - foreach (var index in examineComponent._examineManager.Indexes.OfType() + foreach (var index in examineComponent._examineManager.Indexes.OfType() //filter the indexers .Where(x => x.EnableDefaultEventHandler)) { @@ -798,7 +808,7 @@ namespace Umbraco.Web.Search public static void Execute(ExamineComponent examineComponent, int id, bool keepIfUnpublished) { var strId = id.ToString(CultureInfo.InvariantCulture); - foreach (var index in examineComponent._examineManager.Indexes.OfType() + foreach (var index in examineComponent._examineManager.Indexes.OfType() .Where(x => (keepIfUnpublished && !x.PublishedValuesOnly) || !keepIfUnpublished) .Where(x => x.EnableDefaultEventHandler)) diff --git a/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs b/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs index 32c8d43575..560fb19f09 100644 --- a/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs +++ b/src/Umbraco.Web/Search/GenericIndexDiagnostics.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Search try { var searcher = _index.GetSearcher(); - var result = searcher.Search("test", false); + var result = searcher.Search("test"); return Attempt.Succeed(); //if we can search we'll assume it's healthy } catch (Exception e) diff --git a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs index 3723dedc48..4296176abf 100644 --- a/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs +++ b/src/Umbraco.Web/Search/UmbracoIndexesCreator.cs @@ -59,8 +59,7 @@ namespace Umbraco.Web.Search { var index = new UmbracoContentIndex( Constants.UmbracoIndexes.InternalIndexName, - //fixme - how to deal with languages like in UmbracoContentIndexer.CreateFieldValueTypes - UmbracoExamineIndex.UmbracoIndexFieldDefinitions, + new UmbracoFieldDefinitionCollection(), CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.InternalIndexPath), new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, @@ -73,8 +72,7 @@ namespace Umbraco.Web.Search { var index = new UmbracoContentIndex( Constants.UmbracoIndexes.ExternalIndexName, - //fixme - how to deal with languages like in UmbracoContentIndexer.CreateFieldValueTypes - UmbracoExamineIndex.UmbracoIndexFieldDefinitions, + new UmbracoFieldDefinitionCollection(), CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.ExternalIndexPath), new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_30), ProfilingLogger, @@ -87,8 +85,7 @@ namespace Umbraco.Web.Search { var index = new UmbracoMemberIndex( Constants.UmbracoIndexes.MembersIndexName, - //fixme - how to deal with languages like in UmbracoContentIndexer.CreateFieldValueTypes - UmbracoExamineIndex.UmbracoIndexFieldDefinitions, + new UmbracoFieldDefinitionCollection(), CreateFileSystemLuceneDirectory(Constants.UmbracoIndexes.MembersIndexPath), new CultureInvariantWhitespaceAnalyzer(), ProfilingLogger, diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 4034dd2be6..e2df681122 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -62,7 +62,7 @@ - + 2.6.2.25 From 5e6320730d67415b7b69ac102eff61c7c41bc664 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 13 Dec 2018 18:18:04 +1100 Subject: [PATCH 14/45] Updates nuspec to latest examine --- build/NuSpecs/UmbracoCms.Web.nuspec | 3 +-- src/Umbraco.Web/Search/ExamineComponent.cs | 11 +---------- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index adf090c69b..d3fad9a71d 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -25,8 +25,7 @@ - - + diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 3061a668ca..0a18192cfd 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -23,6 +23,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Web.Scheduling; using System.Threading.Tasks; using Examine.LuceneEngine.Directories; +using Examine.LuceneEngine.Indexing; using LightInject; using Umbraco.Core.Composing; using Umbraco.Core.Strings; @@ -170,16 +171,6 @@ namespace Umbraco.Web.Search RebuildIndexes(indexRebuilder, profilingLogger.Logger, true, 5000); } - private void TestExtending(IExamineManager examineManager) - { - if (examineManager.TryGetIndex("Test", out var index) && index is LuceneIndex umbIndex) - { - umbIndex.FieldValueTypeCollection.ValueTypeFactories.TryAdd() - umbIndex.FieldDefinitionCollection.TryAdd("productName_es-es", new FieldDefinition("productName_es-es", "")); - } - } - - /// /// Called to rebuild empty indexes on startup /// From 7159bca71fbccacc31ed3eb9e681c11676d13823 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 13 Dec 2018 18:24:06 +1100 Subject: [PATCH 15/45] Update to latest examine api changes --- src/Umbraco.Examine/Umbraco.Examine.csproj | 2 +- src/Umbraco.Examine/UmbracoContentIndex.cs | 2 +- src/Umbraco.Examine/UmbracoExamineIndex.cs | 10 +++++----- src/Umbraco.Examine/UmbracoMemberIndex.cs | 4 ++-- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 3c2f5a8ea6..bf404cf890 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -48,7 +48,7 @@ - + diff --git a/src/Umbraco.Examine/UmbracoContentIndex.cs b/src/Umbraco.Examine/UmbracoContentIndex.cs index ac9683f3f2..499de741d8 100644 --- a/src/Umbraco.Examine/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine/UmbracoContentIndex.cs @@ -97,7 +97,7 @@ namespace Umbraco.Examine ProfilingLogger profilingLogger, ILocalizationService languageService, IContentValueSetValidator validator, - IReadOnlyDictionary> indexValueTypes = null) + IReadOnlyDictionary indexValueTypes = null) : base(name, fieldDefinitions, luceneDirectory, defaultAnalyzer, profilingLogger, validator, indexValueTypes) { if (validator == null) throw new ArgumentNullException(nameof(validator)); diff --git a/src/Umbraco.Examine/UmbracoExamineIndex.cs b/src/Umbraco.Examine/UmbracoExamineIndex.cs index 8d84e417d4..9cf4d05c75 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndex.cs @@ -72,7 +72,7 @@ namespace Umbraco.Examine Analyzer defaultAnalyzer, ProfilingLogger profilingLogger, IValueSetValidator validator = null, - IReadOnlyDictionary> indexValueTypes = null) + IReadOnlyDictionary indexValueTypes = null) : base(name, fieldDefinitions, luceneDirectory, defaultAnalyzer, validator, indexValueTypes) { ProfilingLogger = profilingLogger ?? throw new ArgumentNullException(nameof(profilingLogger)); @@ -125,14 +125,14 @@ namespace Umbraco.Examine /// /// /// - protected override FieldValueTypeCollection CreateFieldValueTypes(IReadOnlyDictionary> indexValueTypesFactory = null) + protected override FieldValueTypeCollection CreateFieldValueTypes(IReadOnlyDictionary indexValueTypesFactory = null) { //if config based then ensure the value types else it's assumed these were passed in via ctor if (_configBased) { foreach (var field in UmbracoIndexFieldDefinitions) { - FieldDefinitionCollection.TryAdd(field.Name, field); + FieldDefinitionCollection.TryAdd(field); } } @@ -200,7 +200,7 @@ namespace Umbraco.Examine ConfigIndexCriteria = CreateFieldDefinitionsFromConfig(indexSet); foreach (var fieldDefinition in ConfigIndexCriteria.StandardFields.Union(ConfigIndexCriteria.UserFields)) { - FieldDefinitionCollection.TryAdd(fieldDefinition.Name, fieldDefinition); + FieldDefinitionCollection.TryAdd(fieldDefinition); } found = true; break; @@ -228,7 +228,7 @@ namespace Umbraco.Examine ConfigIndexCriteria = CreateFieldDefinitionsFromConfig(indexSet); foreach (var fieldDefinition in ConfigIndexCriteria.StandardFields.Union(ConfigIndexCriteria.UserFields)) { - FieldDefinitionCollection.TryAdd(fieldDefinition.Name, fieldDefinition); + FieldDefinitionCollection.TryAdd(fieldDefinition); } } } diff --git a/src/Umbraco.Examine/UmbracoMemberIndex.cs b/src/Umbraco.Examine/UmbracoMemberIndex.cs index 43962855c9..27490e0e18 100644 --- a/src/Umbraco.Examine/UmbracoMemberIndex.cs +++ b/src/Umbraco.Examine/UmbracoMemberIndex.cs @@ -64,10 +64,10 @@ namespace Umbraco.Examine /// /// /// - protected override FieldValueTypeCollection CreateFieldValueTypes(IReadOnlyDictionary> indexValueTypesFactory = null) + protected override FieldValueTypeCollection CreateFieldValueTypes(IReadOnlyDictionary indexValueTypesFactory = null) { var keyDef = new FieldDefinition("__key", FieldDefinitionTypes.Raw); - FieldDefinitionCollection.TryAdd(keyDef.Name, keyDef); + FieldDefinitionCollection.TryAdd(keyDef); return base.CreateFieldValueTypes(indexValueTypesFactory); } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index b31a78a08f..e7cc0c54aa 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -77,7 +77,7 @@ - + 1.8.9 diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 60e1324969..4bd30fa383 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -88,7 +88,7 @@ - + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index ee27a05641..3a3f9ceedd 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -62,7 +62,7 @@ - + 2.6.2.25 From 2060f69ab07d692c6a83a7135a0d22fe6a924e15 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 13 Dec 2018 21:09:56 +1100 Subject: [PATCH 16/45] Allow configuring a DirectoryFactory for all LuceneIndexes created with LuceneIndexCreator --- build/NuSpecs/UmbracoCms.Web.nuspec | 2 +- src/Umbraco.Core/Composing/TypeFinder.cs | 2 ++ src/Umbraco.Examine/LuceneIndexCreator.cs | 33 +++++++++++++++++----- src/Umbraco.Examine/Umbraco.Examine.csproj | 2 +- src/Umbraco.Examine/UmbracoContentIndex.cs | 5 +--- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 8 files changed, 34 insertions(+), 16 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index d3fad9a71d..1693e0a7d1 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -25,7 +25,7 @@ - + diff --git a/src/Umbraco.Core/Composing/TypeFinder.cs b/src/Umbraco.Core/Composing/TypeFinder.cs index 9604f599cf..86d3863994 100644 --- a/src/Umbraco.Core/Composing/TypeFinder.cs +++ b/src/Umbraco.Core/Composing/TypeFinder.cs @@ -618,6 +618,8 @@ namespace Umbraco.Core.Composing // doesn't actualy load in all assemblies, only the types that have been referenced so far. // However, in a web context, the BuildManager will have executed which will force all assemblies // to be loaded so it's fine for now. + // It could be fairly easy to parse the typeName to get the assembly name and then do Assembly.Load and + // find the type from there. //now try fall back procedures. type = Type.GetType(typeName); diff --git a/src/Umbraco.Examine/LuceneIndexCreator.cs b/src/Umbraco.Examine/LuceneIndexCreator.cs index 572de1e8a8..0e83b37dc5 100644 --- a/src/Umbraco.Examine/LuceneIndexCreator.cs +++ b/src/Umbraco.Examine/LuceneIndexCreator.cs @@ -1,8 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Configuration; using System.IO; using Examine; using Examine.LuceneEngine.Directories; using Lucene.Net.Store; +using Umbraco.Core; +using Umbraco.Core.Composing; using Umbraco.Core.IO; namespace Umbraco.Examine @@ -18,17 +22,30 @@ namespace Umbraco.Examine /// /// Creates a file system based Lucene with the correct locking guidelines for Umbraco /// - /// + /// + /// The folder name to store the index (single word, not a fully qualified folder) (i.e. Internal) + /// /// - public virtual Lucene.Net.Store.Directory CreateFileSystemLuceneDirectory(string name) + public virtual Lucene.Net.Store.Directory CreateFileSystemLuceneDirectory(string folderName) { - //TODO: We should have a single AppSetting to be able to specify a default DirectoryFactory so we can have a single - //setting to configure all indexes that use this to easily swap the directory to Sync/%temp%/blog, etc... - - var dirInfo = new DirectoryInfo(Path.Combine(IOHelper.MapPath(SystemDirectories.Data), "TEMP", "ExamineIndexes", name)); + + var dirInfo = new DirectoryInfo(Path.Combine(IOHelper.MapPath(SystemDirectories.Data), "TEMP", "ExamineIndexes", folderName)); if (!dirInfo.Exists) System.IO.Directory.CreateDirectory(dirInfo.FullName); + //check if there's a configured directory factory, if so create it and use that to create the lucene dir + var configuredDirectoryFactory = ConfigurationManager.AppSettings["Umbraco.Examine.LuceneDirectoryFactory"]; + if (!configuredDirectoryFactory.IsNullOrWhiteSpace()) + { + //this should be a fully qualified type + var factoryType = TypeFinder.GetTypeByName(configuredDirectoryFactory); + if (factoryType == null) throw new NullReferenceException("No directory type found for value: " + configuredDirectoryFactory); + var directoryFactory = (IDirectoryFactory)Activator.CreateInstance(factoryType); + return directoryFactory.CreateDirectory(dirInfo); + } + + //no dir factory, just create a normal fs directory + var luceneDir = new SimpleFSDirectory(dirInfo); //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain @@ -38,6 +55,8 @@ namespace Umbraco.Examine // however, we are setting the DefaultLockFactory in startup so we'll use that instead since it can be managed globally. luceneDir.SetLockFactory(DirectoryFactory.DefaultLockFactory(dirInfo)); return luceneDir; + + } } } diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index bf404cf890..aa00f29f4a 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -48,7 +48,7 @@ - + diff --git a/src/Umbraco.Examine/UmbracoContentIndex.cs b/src/Umbraco.Examine/UmbracoContentIndex.cs index 499de741d8..3231511936 100644 --- a/src/Umbraco.Examine/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine/UmbracoContentIndex.cs @@ -246,10 +246,7 @@ namespace Umbraco.Examine ProfilingLogger.Logger.Debug(GetType(), "DeleteFromIndex with query: {Query} (found {TotalItems} results)", rawQuery, results.TotalItemCount); //need to queue a delete item for each one found - foreach (var r in results) - { - QueueIndexOperation(new IndexOperation(new ValueSet(r.Id), IndexOperationType.Delete)); - } + QueueIndexOperation(results.Select(r => new IndexOperation(new ValueSet(r.Id), IndexOperationType.Delete))); base.PerformDeleteFromIndex(nodeId, onComplete); } diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index e7cc0c54aa..4112be45ca 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -77,7 +77,7 @@ - + 1.8.9 diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 4bd30fa383..cde23b0df6 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -88,7 +88,7 @@ - + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 3a3f9ceedd..54b5a025bf 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -62,7 +62,7 @@ - + 2.6.2.25 From c129375dd0744df87aff2f299500f24ea4ebf977 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 13 Dec 2018 22:33:41 +1100 Subject: [PATCH 17/45] more bug fixes --- build/NuSpecs/UmbracoCms.Web.nuspec | 2 +- src/Umbraco.Examine/Umbraco.Examine.csproj | 3 +- src/Umbraco.Examine/UmbracoContentIndex.cs | 41 +---------- src/Umbraco.Examine/UmbracoExamineIndex.cs | 68 ++++++------------- .../UmbracoFieldDefinitionCollection.cs | 43 ++++++++++++ src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 8 files changed, 72 insertions(+), 91 deletions(-) create mode 100644 src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 1693e0a7d1..d36c4a240a 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -25,7 +25,7 @@ - + diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index aa00f29f4a..56e9b1a928 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -48,7 +48,7 @@ - + @@ -90,6 +90,7 @@ + Properties\SolutionInfo.cs diff --git a/src/Umbraco.Examine/UmbracoContentIndex.cs b/src/Umbraco.Examine/UmbracoContentIndex.cs index 3231511936..9134c691f4 100644 --- a/src/Umbraco.Examine/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine/UmbracoContentIndex.cs @@ -18,45 +18,6 @@ using Examine.LuceneEngine; namespace Umbraco.Examine { - /// - /// Custom allowing dynamic creation of - /// - public class UmbracoFieldDefinitionCollection : FieldDefinitionCollection - { - - public UmbracoFieldDefinitionCollection() - : base(UmbracoExamineIndex.UmbracoIndexFieldDefinitions) - { - } - - ///// - ///// Overridden to dynamically add field definitions for culture variations - ///// - ///// - ///// - ///// - //public override bool TryGetValue(string fieldName, out FieldDefinition fieldDefinition) - //{ - // var result = base.TryGetValue(fieldName, out fieldDefinition); - // if (result) return true; - - // //if the fieldName is not suffixed with _iso-Code - // var underscoreIndex = fieldName.LastIndexOf('_'); - // if (underscoreIndex == -1) return false; - - - - // var isoCode = fieldName.Substring(underscoreIndex); - // if (isoCode.Length < 6) return false; //invalid isoCode - - // var hyphenIndex = isoCode.IndexOf('-'); - // if (hyphenIndex != 3) return false; //invalid isoCode - - // //we'll assume this is a valid isoCode - - //} - } - /// /// An indexer for Umbraco content and media /// @@ -161,7 +122,7 @@ namespace Umbraco.Examine //anywhere else in this class Current.Services.PublicAccessService, parentId, - ConfigIndexCriteria.IncludeItemTypes, ConfigIndexCriteria.ExcludeItemTypes); + ConfigIndexCriteria?.IncludeItemTypes, ConfigIndexCriteria?.ExcludeItemTypes); PublishedValuesOnly = supportUnpublished; } diff --git a/src/Umbraco.Examine/UmbracoExamineIndex.cs b/src/Umbraco.Examine/UmbracoExamineIndex.cs index 9cf4d05c75..3a7a21bdb3 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndex.cs @@ -20,8 +20,7 @@ namespace Umbraco.Examine { /// - /// An abstract provider containing the basic functionality to be able to query against - /// Umbraco data. + /// An abstract provider containing the basic functionality to be able to query against Umbraco data. /// public abstract class UmbracoExamineIndex : LuceneIndex, IUmbracoIndex, IIndexDiagnostics { @@ -53,6 +52,7 @@ namespace Umbraco.Examine { ProfilingLogger = Current.ProfilingLogger; _configBased = true; + _diagnostics = new UmbracoExamineIndexDiagnostics(this, ProfilingLogger.Logger); } /// @@ -120,26 +120,6 @@ namespace Umbraco.Examine protected ProfilingLogger ProfilingLogger { get; } - /// - /// Overridden to ensure that the umbraco system field definitions are in place - /// - /// - /// - protected override FieldValueTypeCollection CreateFieldValueTypes(IReadOnlyDictionary indexValueTypesFactory = null) - { - //if config based then ensure the value types else it's assumed these were passed in via ctor - if (_configBased) - { - foreach (var field in UmbracoIndexFieldDefinitions) - { - FieldDefinitionCollection.TryAdd(field); - } - } - - - return base.CreateFieldValueTypes(indexValueTypesFactory); - } - /// /// When set to true Umbraco will keep the index in sync with Umbraco data automatically /// @@ -174,12 +154,15 @@ namespace Umbraco.Examine EnableDefaultEventHandler = enabled; } - //Need to check if the index set or IndexerData is specified... - if (config["indexSet"] == null && FieldDefinitionCollection.Count == 0) + //this is config based, so add the default definitions + foreach (var field in UmbracoIndexFieldDefinitions) { - //if we don't have either, then we'll try to set the index set by naming conventions - var found = false; + FieldDefinitionCollection.TryAdd(field); + } + //Need to check if the index set is specified... + if (config["indexSet"] == null) + { var possibleSuffixes = new[] {"Index", "Indexer"}; foreach (var suffix in possibleSuffixes) { @@ -200,36 +183,29 @@ namespace Umbraco.Examine ConfigIndexCriteria = CreateFieldDefinitionsFromConfig(indexSet); foreach (var fieldDefinition in ConfigIndexCriteria.StandardFields.Union(ConfigIndexCriteria.UserFields)) { - FieldDefinitionCollection.TryAdd(fieldDefinition); + //replace any existing or add + FieldDefinitionCollection.AddOrUpdate(fieldDefinition); } - found = true; break; } - - if (!found) - throw new ArgumentNullException("indexSet on LuceneExamineIndexer provider has not been set in configuration and/or the IndexerData property has not been explicitly set"); - } - else if (config["indexSet"] != null) + else { //if an index set is specified, ensure it exists and initialize the indexer based on the set if (IndexSets.Instance.Sets[config["indexSet"]] == null) - { throw new ArgumentException("The indexSet specified for the LuceneExamineIndexer provider does not exist"); - } - else + + IndexSetName = config["indexSet"]; + + var indexSet = IndexSets.Instance.Sets[IndexSetName]; + + //get the index criteria and ensure folder + ConfigIndexCriteria = CreateFieldDefinitionsFromConfig(indexSet); + foreach (var fieldDefinition in ConfigIndexCriteria.StandardFields.Union(ConfigIndexCriteria.UserFields)) { - IndexSetName = config["indexSet"]; - - var indexSet = IndexSets.Instance.Sets[IndexSetName]; - - //get the index criteria and ensure folder - ConfigIndexCriteria = CreateFieldDefinitionsFromConfig(indexSet); - foreach (var fieldDefinition in ConfigIndexCriteria.StandardFields.Union(ConfigIndexCriteria.UserFields)) - { - FieldDefinitionCollection.TryAdd(fieldDefinition); - } + //replace any existing or add + FieldDefinitionCollection.AddOrUpdate(fieldDefinition); } } diff --git a/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs b/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs new file mode 100644 index 0000000000..ce59501046 --- /dev/null +++ b/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs @@ -0,0 +1,43 @@ +using Examine; + +namespace Umbraco.Examine +{ + /// + /// Custom allowing dynamic creation of + /// + public class UmbracoFieldDefinitionCollection : FieldDefinitionCollection + { + + public UmbracoFieldDefinitionCollection() + : base(UmbracoExamineIndex.UmbracoIndexFieldDefinitions) + { + } + + ///// + ///// Overridden to dynamically add field definitions for culture variations + ///// + ///// + ///// + ///// + //public override bool TryGetValue(string fieldName, out FieldDefinition fieldDefinition) + //{ + // var result = base.TryGetValue(fieldName, out fieldDefinition); + // if (result) return true; + + // //if the fieldName is not suffixed with _iso-Code + // var underscoreIndex = fieldName.LastIndexOf('_'); + // if (underscoreIndex == -1) return false; + + + + // var isoCode = fieldName.Substring(underscoreIndex); + // if (isoCode.Length < 6) return false; //invalid isoCode + + // var hyphenIndex = isoCode.IndexOf('-'); + // if (hyphenIndex != 3) return false; //invalid isoCode + + // //we'll assume this is a valid isoCode + + //} + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 4112be45ca..38c049f7aa 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -77,7 +77,7 @@ - + 1.8.9 diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index cde23b0df6..88155846d7 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -88,7 +88,7 @@ - + diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 54b5a025bf..9a96acbd8c 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -62,7 +62,7 @@ - + 2.6.2.25 From 162f8480d17fa8a3422700f08767fc14fd895522 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 13 Dec 2018 23:17:58 +1100 Subject: [PATCH 18/45] more tweaks to get config based indexes working, updates to IIndexPopulator to have strongly typed association --- build/NuSpecs/UmbracoCms.Web.nuspec | 2 +- src/Umbraco.Examine/ContentIndexPopulator.cs | 5 +---- src/Umbraco.Examine/IIndexPopulator.cs | 10 ++++++---- src/Umbraco.Examine/IndexPopulator.cs | 19 ++++++++++++++++--- src/Umbraco.Examine/IndexRebuilder.cs | 4 ++-- src/Umbraco.Examine/MediaIndexPopulator.cs | 5 +---- src/Umbraco.Examine/MemberIndexPopulator.cs | 4 +--- src/Umbraco.Examine/Umbraco.Examine.csproj | 2 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- src/Umbraco.Tests/UmbracoExamine/IndexTest.cs | 3 --- .../UmbracoExamine/SearchTests.cs | 1 - src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- .../Editors/ExamineManagementController.cs | 12 ++++++------ src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 14 files changed, 38 insertions(+), 35 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index d36c4a240a..11d9004a34 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -14,7 +14,7 @@ Contains the core assemblies needed to run Umbraco Cms en-US umbraco - + - + diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 38c049f7aa..810ebe5ab4 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -77,7 +77,7 @@ - + 1.8.9 diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 35e3524459..2f0adfcb25 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -130,8 +130,6 @@ namespace Umbraco.Tests.UmbracoExamine validator: new ContentValueSetValidator(false))) using (indexer.ProcessNonAsync()) { - contentRebuilder.RegisterIndex(indexer.Name); - mediaRebuilder.RegisterIndex(indexer.Name); var searcher = indexer.GetSearcher(); @@ -283,7 +281,6 @@ namespace Umbraco.Tests.UmbracoExamine validator: new ContentValueSetValidator(false))) using (indexer.ProcessNonAsync()) { - rebuilder.RegisterIndex(indexer.Name); var searcher = indexer.GetSearcher(); diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index a01d49cc22..8156f640d6 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -62,7 +62,6 @@ namespace Umbraco.Tests.UmbracoExamine using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir)) using (indexer.ProcessNonAsync()) { - rebuilder.RegisterIndex(indexer.Name); indexer.CreateIndex(); rebuilder.Populate(indexer); diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 88155846d7..4a34e23afb 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -88,7 +88,7 @@ - + diff --git a/src/Umbraco.Web/Editors/ExamineManagementController.cs b/src/Umbraco.Web/Editors/ExamineManagementController.cs index 6667510b49..bce2c4a438 100644 --- a/src/Umbraco.Web/Editors/ExamineManagementController.cs +++ b/src/Umbraco.Web/Editors/ExamineManagementController.cs @@ -109,7 +109,7 @@ namespace Umbraco.Web.Editors if (!validate.IsSuccessStatusCode) throw new HttpResponseException(validate); - validate = ValidatePopulator(indexName); + validate = ValidatePopulator(index); if (!validate.IsSuccessStatusCode) throw new HttpResponseException(validate); @@ -134,7 +134,7 @@ namespace Umbraco.Web.Editors if (!validate.IsSuccessStatusCode) return validate; - validate = ValidatePopulator(indexName); + validate = ValidatePopulator(index); if (!validate.IsSuccessStatusCode) return validate; @@ -201,7 +201,7 @@ namespace Umbraco.Web.Editors Name = indexName, HealthStatus = isHealth.Success ? (isHealth.Result ?? "Healthy") : (isHealth.Result ?? "Unhealthy"), ProviderProperties = properties, - CanRebuild = _indexRebuilder.CanRebuild(indexName) + CanRebuild = _indexRebuilder.CanRebuild(index) }; @@ -228,13 +228,13 @@ namespace Umbraco.Web.Editors return response1; } - private HttpResponseMessage ValidatePopulator(string indexName) + private HttpResponseMessage ValidatePopulator(IIndex index) { - if (_indexRebuilder.CanRebuild(indexName)) + if (_indexRebuilder.CanRebuild(index)) return Request.CreateResponse(HttpStatusCode.OK); var response = Request.CreateResponse(HttpStatusCode.BadRequest); - response.Content = new StringContent($"The index {indexName} cannot be rebuilt because it does not have an associated {typeof(IIndexPopulator)}"); + response.Content = new StringContent($"The index {index.Name} cannot be rebuilt because it does not have an associated {typeof(IIndexPopulator)}"); response.ReasonPhrase = "Index cannot be rebuilt"; return response; } diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 9a96acbd8c..8006277c73 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -62,7 +62,7 @@ - + 2.6.2.25 From d38e4e6e3450d32c2b6f309355db89332bff64ee Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 14 Dec 2018 11:32:20 +1100 Subject: [PATCH 19/45] latest examine, fixes sortability --- build/NuSpecs/UmbracoCms.Web.nuspec | 2 +- src/Umbraco.Examine/Umbraco.Examine.csproj | 2 +- src/Umbraco.Examine/UmbracoExamineIndex.cs | 34 ++---------------- .../UmbracoFieldDefinitionCollection.cs | 36 +++++++++++++++++-- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- .../UmbracoExamine/SearchTests.cs | 3 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- .../XmlPublishedCache/PublishedMediaCache.cs | 1 + src/Umbraco.Web/Umbraco.Web.csproj | 2 +- 9 files changed, 44 insertions(+), 40 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index 11d9004a34..aacd55d4be 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -25,7 +25,7 @@ - + diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 4258e14aa0..75c3f5a230 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -48,7 +48,7 @@ - + diff --git a/src/Umbraco.Examine/UmbracoExamineIndex.cs b/src/Umbraco.Examine/UmbracoExamineIndex.cs index 3a7a21bdb3..1933874fc7 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndex.cs @@ -86,37 +86,7 @@ namespace Umbraco.Examine private readonly bool _configBased = false; - /// - /// A type that defines the type of index for each Umbraco field (non user defined fields) - /// Alot of standard umbraco fields shouldn't be tokenized or even indexed, just stored into lucene - /// for retreival after searching. - /// - public static readonly FieldDefinition[] UmbracoIndexFieldDefinitions = - { - new FieldDefinition("parentID", FieldDefinitionTypes.Integer), - new FieldDefinition("level", FieldDefinitionTypes.Integer), - new FieldDefinition("writerID", FieldDefinitionTypes.Integer), - new FieldDefinition("creatorID", FieldDefinitionTypes.Integer), - new FieldDefinition("sortOrder", FieldDefinitionTypes.Integer), - new FieldDefinition("template", FieldDefinitionTypes.Integer), - - new FieldDefinition("createDate", FieldDefinitionTypes.DateTime), - new FieldDefinition("updateDate", FieldDefinitionTypes.DateTime), - - new FieldDefinition("key", FieldDefinitionTypes.InvariantCultureIgnoreCase), - new FieldDefinition("version", FieldDefinitionTypes.Raw), - new FieldDefinition("nodeType", FieldDefinitionTypes.InvariantCultureIgnoreCase), - new FieldDefinition("template", FieldDefinitionTypes.Raw), - new FieldDefinition("urlName", FieldDefinitionTypes.InvariantCultureIgnoreCase), - new FieldDefinition("path", FieldDefinitionTypes.Raw), - - new FieldDefinition("email", FieldDefinitionTypes.EmailAddress), - - new FieldDefinition(PublishedFieldName, FieldDefinitionTypes.Raw), - new FieldDefinition(NodeKeyFieldName, FieldDefinitionTypes.Raw), - new FieldDefinition(IndexPathFieldName, FieldDefinitionTypes.Raw), - new FieldDefinition(IconFieldName, FieldDefinitionTypes.Raw) - }; + protected ProfilingLogger ProfilingLogger { get; } @@ -155,7 +125,7 @@ namespace Umbraco.Examine } //this is config based, so add the default definitions - foreach (var field in UmbracoIndexFieldDefinitions) + foreach (var field in UmbracoFieldDefinitionCollection.UmbracoIndexFieldDefinitions) { FieldDefinitionCollection.TryAdd(field); } diff --git a/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs b/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs index ce59501046..97d1f68727 100644 --- a/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs +++ b/src/Umbraco.Examine/UmbracoFieldDefinitionCollection.cs @@ -9,10 +9,42 @@ namespace Umbraco.Examine { public UmbracoFieldDefinitionCollection() - : base(UmbracoExamineIndex.UmbracoIndexFieldDefinitions) + : base(UmbracoIndexFieldDefinitions) { } + /// + /// A type that defines the type of index for each Umbraco field (non user defined fields) + /// Alot of standard umbraco fields shouldn't be tokenized or even indexed, just stored into lucene + /// for retreival after searching. + /// + public static readonly FieldDefinition[] UmbracoIndexFieldDefinitions = + { + new FieldDefinition("parentID", FieldDefinitionTypes.Integer), + new FieldDefinition("level", FieldDefinitionTypes.Integer), + new FieldDefinition("writerID", FieldDefinitionTypes.Integer), + new FieldDefinition("creatorID", FieldDefinitionTypes.Integer), + new FieldDefinition("sortOrder", FieldDefinitionTypes.Integer), + new FieldDefinition("template", FieldDefinitionTypes.Integer), + + new FieldDefinition("createDate", FieldDefinitionTypes.DateTime), + new FieldDefinition("updateDate", FieldDefinitionTypes.DateTime), + + new FieldDefinition("key", FieldDefinitionTypes.InvariantCultureIgnoreCase), + new FieldDefinition("version", FieldDefinitionTypes.Raw), + new FieldDefinition("nodeType", FieldDefinitionTypes.InvariantCultureIgnoreCase), + new FieldDefinition("template", FieldDefinitionTypes.Raw), + new FieldDefinition("urlName", FieldDefinitionTypes.InvariantCultureIgnoreCase), + new FieldDefinition("path", FieldDefinitionTypes.Raw), + + new FieldDefinition("email", FieldDefinitionTypes.EmailAddress), + + new FieldDefinition(UmbracoExamineIndex.PublishedFieldName, FieldDefinitionTypes.Raw), + new FieldDefinition(UmbracoExamineIndex.NodeKeyFieldName, FieldDefinitionTypes.Raw), + new FieldDefinition(UmbracoExamineIndex.IndexPathFieldName, FieldDefinitionTypes.Raw), + new FieldDefinition(UmbracoExamineIndex.IconFieldName, FieldDefinitionTypes.Raw) + }; + ///// ///// Overridden to dynamically add field definitions for culture variations ///// @@ -40,4 +72,4 @@ namespace Umbraco.Examine //} } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 810ebe5ab4..2d20c588b5 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -77,7 +77,7 @@ - + 1.8.9 diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index 8156f640d6..3e96e30c6a 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -5,6 +5,7 @@ using LightInject; using Examine; using NUnit.Framework; using Examine.LuceneEngine.SearchCriteria; +using Examine.SearchCriteria; using Moq; using Umbraco.Core.Models; using Umbraco.Core.Persistence; @@ -74,7 +75,7 @@ namespace Umbraco.Tests.UmbracoExamine var stringSortedCriteria = searcher.CreateCriteria() .ParentId(1148).And() - .OrderBy("sortOrder"); //will default to string + .OrderBy(new SortableField("sortOrder"));//will default to string var stringSortedResult = searcher.Search(stringSortedCriteria.Compile()); Assert.AreEqual(12, numberSortedResult.TotalItemCount); diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 4a34e23afb..465544960c 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -88,7 +88,7 @@ - + diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index f203d5d2c9..3f776830a6 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -8,6 +8,7 @@ using System.Xml.XPath; using Examine; using Examine.LuceneEngine.SearchCriteria; using Examine.Providers; +using Examine.SearchCriteria; using Lucene.Net.Store; using Umbraco.Core; using Umbraco.Core.Logging; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 8006277c73..eb6eab9829 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -62,7 +62,7 @@ - + 2.6.2.25 From 4976782247f613dea5a769dbc4073644996a3088 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 17 Dec 2018 12:17:03 +1100 Subject: [PATCH 20/45] Updates search published content search extensions to correspond with new examine api changes, updates examine api changes --- build/NuSpecs/UmbracoCms.Web.nuspec | 2 +- src/Umbraco.Examine/Umbraco.Examine.csproj | 2 +- src/Umbraco.Examine/UmbracoContentIndex.cs | 6 +- .../UmbracoExamineExtensions.cs | 8 +-- .../PublishedContent/PublishedMediaTests.cs | 4 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 2 +- .../UmbracoExamine/EventsTest.cs | 2 +- src/Umbraco.Tests/UmbracoExamine/IndexTest.cs | 22 ++++---- .../UmbracoExamine/SearchTests.cs | 19 +++---- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 2 +- .../Editors/ExamineManagementController.cs | 2 +- src/Umbraco.Web/IPublishedContentQuery.cs | 13 +++-- .../XmlPublishedCache/PublishedMediaCache.cs | 20 +++---- src/Umbraco.Web/PublishedContentExtensions.cs | 47 ++++++---------- src/Umbraco.Web/PublishedContentQuery.cs | 30 +++------- src/Umbraco.Web/Search/ExamineComponent.cs | 4 +- src/Umbraco.Web/Search/UmbracoTreeSearcher.cs | 6 +- src/Umbraco.Web/Umbraco.Web.csproj | 2 +- src/Umbraco.Web/UmbracoHelper.cs | 56 ------------------- 19 files changed, 79 insertions(+), 170 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Web.nuspec b/build/NuSpecs/UmbracoCms.Web.nuspec index aacd55d4be..51d7e3b8d0 100644 --- a/build/NuSpecs/UmbracoCms.Web.nuspec +++ b/build/NuSpecs/UmbracoCms.Web.nuspec @@ -25,7 +25,7 @@ - + diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index 75c3f5a230..a037c3028e 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -48,7 +48,7 @@ - + diff --git a/src/Umbraco.Examine/UmbracoContentIndex.cs b/src/Umbraco.Examine/UmbracoContentIndex.cs index 9134c691f4..c584f5bf51 100644 --- a/src/Umbraco.Examine/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine/UmbracoContentIndex.cs @@ -200,9 +200,9 @@ namespace Umbraco.Examine var descendantPath = $@"\-1\,*{nodeId}\,*"; var rawQuery = $"{IndexPathFieldName}:{descendantPath}"; var searcher = GetSearcher(); - var c = searcher.CreateCriteria(); - var filtered = c.RawQuery(rawQuery); - var results = searcher.Search(filtered); + var c = searcher.CreateQuery(); + var filtered = c.NativeQuery(rawQuery); + var results = filtered.Execute(); ProfilingLogger.Logger.Debug(GetType(), "DeleteFromIndex with query: {Query} (found {TotalItems} results)", rawQuery, results.TotalItemCount); diff --git a/src/Umbraco.Examine/UmbracoExamineExtensions.cs b/src/Umbraco.Examine/UmbracoExamineExtensions.cs index 8be5a6c1e3..f33b7587e0 100644 --- a/src/Umbraco.Examine/UmbracoExamineExtensions.cs +++ b/src/Umbraco.Examine/UmbracoExamineExtensions.cs @@ -1,10 +1,6 @@ -using System; -using System.ComponentModel; -using System.Web; -using Examine.LuceneEngine.SearchCriteria; -using Examine.SearchCriteria; +using Examine.LuceneEngine.Search; +using Examine.Search; using Umbraco.Core; -using Umbraco.Examine.Config; namespace Umbraco.Examine { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs index d5fdd2bf0c..309ce1c0fb 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedMediaTests.cs @@ -173,9 +173,9 @@ namespace Umbraco.Tests.PublishedContent //ensure it still exists in the index (raw examine search) - var criteria = searcher.CreateCriteria(); + var criteria = searcher.CreateQuery(); var filter = criteria.Id(3113); - var found = searcher.Search(filter.Compile()); + var found = filter.Execute(); Assert.IsNotNull(found); Assert.AreEqual(1, found.TotalItemCount); diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index a9f38ba391..35a51e0584 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -77,7 +77,7 @@ - + 1.8.9 diff --git a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs index 47fa32cbaa..ed5ae07fc0 100644 --- a/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/EventsTest.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.UmbracoExamine var valueSet = node.ConvertToValueSet(IndexTypes.Content); indexer.IndexItems(new[] { valueSet }); - var found = searcher.Search(searcher.CreateCriteria().Id((string)node.Attribute("id")).Compile()); + var found = searcher.CreateQuery().Id((string)node.Attribute("id")).Execute(); Assert.AreEqual(0, found.TotalItemCount); } diff --git a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs index 2f0adfcb25..1bc51b6173 100644 --- a/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs +++ b/src/Umbraco.Tests/UmbracoExamine/IndexTest.cs @@ -105,7 +105,7 @@ namespace Umbraco.Tests.UmbracoExamine var searcher = indexer.GetSearcher(); - var results = searcher.Search(searcher.CreateCriteria().Id(555).Compile()); + var results = searcher.CreateQuery().Id(555).Execute(); Assert.AreEqual(1, results.TotalItemCount); var result = results.First(); @@ -137,7 +137,7 @@ namespace Umbraco.Tests.UmbracoExamine contentRebuilder.Populate(indexer); mediaRebuilder.Populate(indexer); - var result = searcher.Search(searcher.CreateCriteria().All().Compile()); + var result = searcher.CreateQuery().All().Execute(); Assert.AreEqual(29, result.TotalItemCount); } @@ -208,7 +208,7 @@ namespace Umbraco.Tests.UmbracoExamine indexer.IndexItem(node.ConvertToValueSet(IndexTypes.Media)); //it will not exist because it exists under 2222 - var results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); + var results = searcher.CreateQuery().Id(2112).Execute(); Assert.AreEqual(0, results.Count()); //now mimic moving 2112 to 1116 @@ -220,7 +220,7 @@ namespace Umbraco.Tests.UmbracoExamine indexer.IndexItems(new[] { node.ConvertToValueSet(IndexTypes.Media) }); //now ensure it exists - results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); + results = searcher.CreateQuery().Id(2112).Execute(); Assert.AreEqual(1, results.Count()); } } @@ -251,7 +251,7 @@ namespace Umbraco.Tests.UmbracoExamine indexer1.IndexItem(node.ConvertToValueSet(IndexTypes.Media)); //it will exist because it exists under 2222 - var results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); + var results = searcher.CreateQuery().Id(2112).Execute(); Assert.AreEqual(1, results.Count()); //now mimic moving the node underneath 1116 instead of 2222 @@ -262,7 +262,7 @@ namespace Umbraco.Tests.UmbracoExamine indexer1.IndexItems(new[] { node.ConvertToValueSet(IndexTypes.Media) }); //now ensure it's deleted - results = searcher.Search(searcher.CreateCriteria().Id(2112).Compile()); + results = searcher.CreateQuery().Id(2112).Execute(); Assert.AreEqual(0, results.Count()); } } @@ -287,7 +287,7 @@ namespace Umbraco.Tests.UmbracoExamine //create the whole thing rebuilder.Populate(indexer); - var result = searcher.Search(searcher.CreateCriteria().Field(LuceneIndex.CategoryFieldName, IndexTypes.Content).Compile()); + var result = searcher.CreateQuery().Field(LuceneIndex.CategoryFieldName, IndexTypes.Content).Execute(); Assert.AreEqual(21, result.TotalItemCount); //delete all content @@ -298,13 +298,13 @@ namespace Umbraco.Tests.UmbracoExamine //ensure it's all gone - result = searcher.Search(searcher.CreateCriteria().Field(LuceneIndex.CategoryFieldName, IndexTypes.Content).Compile()); + result = searcher.CreateQuery().Field(LuceneIndex.CategoryFieldName, IndexTypes.Content).Execute(); Assert.AreEqual(0, result.TotalItemCount); //call our indexing methods rebuilder.Populate(indexer); - result = searcher.Search(searcher.CreateCriteria().Field(LuceneIndex.CategoryFieldName, IndexTypes.Content).Compile()); + result = searcher.CreateQuery().Field(LuceneIndex.CategoryFieldName, IndexTypes.Content).Execute(); Assert.AreEqual(21, result.TotalItemCount); } } @@ -332,10 +332,10 @@ namespace Umbraco.Tests.UmbracoExamine indexer.DeleteFromIndex(1140.ToString()); //this node had children: 1141 & 1142, let's ensure they are also removed - var results = searcher.Search(searcher.CreateCriteria().Id(1141).Compile()); + var results = searcher.CreateQuery().Id(1141).Execute(); Assert.AreEqual(0, results.Count()); - results = searcher.Search(searcher.CreateCriteria().Id(1142).Compile()); + results = searcher.CreateQuery().Id(1142).Execute(); Assert.AreEqual(0, results.Count()); } diff --git a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs index 3e96e30c6a..747bab3c6d 100644 --- a/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs +++ b/src/Umbraco.Tests/UmbracoExamine/SearchTests.cs @@ -3,18 +3,15 @@ using System.Collections.Generic; using System.Linq; using LightInject; using Examine; +using Examine.Search; using NUnit.Framework; -using Examine.LuceneEngine.SearchCriteria; -using Examine.SearchCriteria; using Moq; using Umbraco.Core.Models; -using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Services; -using Umbraco.Examine; using Umbraco.Tests.Testing; using Umbraco.Core.PropertyEditors; -using Umbraco.Core.Strings; +using Umbraco.Examine; namespace Umbraco.Tests.UmbracoExamine { @@ -68,15 +65,15 @@ namespace Umbraco.Tests.UmbracoExamine var searcher = indexer.GetSearcher(); - var numberSortedCriteria = searcher.CreateCriteria() - .ParentId(1148).And() + var numberSortedCriteria = searcher.CreateQuery() + .ParentId(1148) .OrderBy(new SortableField("sortOrder", SortType.Int)); - var numberSortedResult = searcher.Search(numberSortedCriteria.Compile()); + var numberSortedResult = numberSortedCriteria.Execute(); - var stringSortedCriteria = searcher.CreateCriteria() - .ParentId(1148).And() + var stringSortedCriteria = searcher.CreateQuery() + .ParentId(1148) .OrderBy(new SortableField("sortOrder"));//will default to string - var stringSortedResult = searcher.Search(stringSortedCriteria.Compile()); + var stringSortedResult = stringSortedCriteria.Execute(); Assert.AreEqual(12, numberSortedResult.TotalItemCount); Assert.AreEqual(12, stringSortedResult.TotalItemCount); diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 465544960c..afb3cfa9d7 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -88,7 +88,7 @@ - + diff --git a/src/Umbraco.Web/Editors/ExamineManagementController.cs b/src/Umbraco.Web/Editors/ExamineManagementController.cs index bce2c4a438..b583babee3 100644 --- a/src/Umbraco.Web/Editors/ExamineManagementController.cs +++ b/src/Umbraco.Web/Editors/ExamineManagementController.cs @@ -74,7 +74,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(msg); var results = Examine.ExamineExtensions.TryParseLuceneQuery(query) - ? searcher.Search(searcher.CreateCriteria().RawQuery(query), maxResults: pageSize * (pageIndex + 1)) + ? searcher.CreateQuery().NativeQuery(query).Execute(maxResults: pageSize * (pageIndex + 1)) : searcher.Search(query, maxResults: pageSize * (pageIndex + 1)); var pagedResults = results.Skip(pageIndex * pageSize); diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index 3ab1c5e3ae..cc388ad094 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Xml.XPath; +using Examine.Search; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; @@ -34,21 +35,21 @@ namespace Umbraco.Web /// /// Searches content. /// - IEnumerable Search(string term, bool useWildCards = true, string indexName = null); + IEnumerable Search(string term, string indexName = null); /// /// Searches content. /// - IEnumerable Search(int skip, int take, out long totalRecords, string term, bool useWildCards = true, string indexName = null); + IEnumerable Search(string term, int skip, int take, out long totalRecords, string indexName = null); /// - /// Searches content. + /// Executes the query and converts the results to PublishedSearchResult. /// - IEnumerable Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.ISearcher searchProvider = null); + IEnumerable Search(IQueryExecutor query); /// - /// Searches content. + /// Executes the query and converts the results to PublishedSearchResult. /// - IEnumerable Search(int skip, int take, out long totalRecords, Examine.SearchCriteria.ISearchCriteria criteria, Examine.ISearcher searcher = null); + IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords); } } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 3f776830a6..229a981510 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -6,9 +6,8 @@ using System.Linq; using System.Threading; using System.Xml.XPath; using Examine; -using Examine.LuceneEngine.SearchCriteria; using Examine.Providers; -using Examine.SearchCriteria; +using Examine.Search; using Lucene.Net.Store; using Umbraco.Core; using Umbraco.Core.Logging; @@ -16,7 +15,6 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Xml; using Umbraco.Examine; -using umbraco; using Umbraco.Core.Cache; using Umbraco.Core.Services; using Umbraco.Web.Composing; @@ -108,10 +106,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // first check in Examine for the cache values // +(+parentID:-1) +__IndexType:media - var criteria = searchProvider.CreateCriteria("media"); + var criteria = searchProvider.CreateQuery("media"); var filter = criteria.ParentId(-1).Not().Field(UmbracoExamineIndex.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); - var result = searchProvider.Search(filter.Compile()); + var result = filter.Execute(); if (result != null) return result.Select(x => CreateFromCacheValues(ConvertFromSearchResult(x))); } @@ -294,10 +292,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache // // note that since the use of the wildcard, it automatically escapes it in Lucene. - var criteria = searchProvider.CreateCriteria("media"); + var criteria = searchProvider.CreateQuery("media"); var filter = criteria.Id(id.ToInvariantString()).Not().Field(UmbracoExamineIndex.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); - var result = searchProvider.Search(filter.Compile()).FirstOrDefault(); + var result = filter.Execute().FirstOrDefault(); if (result != null) return ConvertFromSearchResult(result); } catch (Exception ex) @@ -508,15 +506,15 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache try { //first check in Examine as this is WAY faster - var criteria = searchProvider.CreateCriteria("media"); + var criteria = searchProvider.CreateQuery("media"); - var filter = criteria.ParentId(parentId).Not().Field(UmbracoExamineIndex.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); + var filter = criteria.ParentId(parentId).Not().Field(UmbracoExamineIndex.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()) + .OrderBy(new SortableField("sortOrder", SortType.Int)); //the above filter will create a query like this, NOTE: That since the use of the wildcard, it automatically escapes it in Lucene. //+(+parentId:3113 -__Path:-1,-21,*) +__IndexType:media // sort with the Sort field (updated for 8.0) - var results = searchProvider.Search( - filter.And().OrderBy(new SortableField("sortOrder", SortType.Int)).Compile()); + var results = filter.Execute(); if (results.Any()) { diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index e3291f9ad5..8deec5d92a 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -4,12 +4,13 @@ using System.Data; using System.Linq; using System.Web; using Examine; -using Examine.LuceneEngine.SearchCriteria; +using Examine.Search; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Models; using Umbraco.Core.Services; +using Umbraco.Examine; using Umbraco.Web.Composing; namespace Umbraco.Web @@ -282,7 +283,7 @@ namespace Umbraco.Web #region Search - public static IEnumerable SearchDescendants(this IPublishedContent content, string term, bool useWildCards = true, string indexName = null) + public static IEnumerable SearchDescendants(this IPublishedContent content, string term, string indexName = null) { //fixme: pass in the IExamineManager @@ -292,17 +293,18 @@ namespace Umbraco.Web var searcher = index.GetSearcher(); - var t = term.Escape().Value; - if (useWildCards) - t = term.MultipleCharacterWildcard().Value; + //var t = term.Escape().Value; + //var luceneQuery = "+__Path:(" + content.Path.Replace("-", "\\-") + "*) +" + t; - var luceneQuery = "+__Path:(" + content.Path.Replace("-", "\\-") + "*) +" + t; - var crit = searcher.CreateCriteria().RawQuery(luceneQuery); + var query = searcher.CreateQuery() + .Field(UmbracoExamineIndex.IndexPathFieldName, (content.Path + ",").MultipleCharacterWildcard()) + .And() + .ManagedQuery(term); - return content.Search(crit, searcher); + return query.Execute().ToPublishedSearchResults(UmbracoContext.Current.ContentCache); } - public static IEnumerable SearchChildren(this IPublishedContent content, string term, bool useWildCards = true, string indexName = null) + public static IEnumerable SearchChildren(this IPublishedContent content, string term, string indexName = null) { //fixme: pass in the IExamineManager @@ -312,28 +314,15 @@ namespace Umbraco.Web var searcher = index.GetSearcher(); - var t = term.Escape().Value; - if (useWildCards) - t = term.MultipleCharacterWildcard().Value; + //var t = term.Escape().Value; + //var luceneQuery = "+parentID:" + content.Id + " +" + t; - var luceneQuery = "+parentID:" + content.Id + " +" + t; - var crit = searcher.CreateCriteria().RawQuery(luceneQuery); + var query = searcher.CreateQuery() + .Field("parentID", content.Id) + .And() + .ManagedQuery(term); - return content.Search(crit, searcher); - } - - public static IEnumerable Search(this IPublishedContent content, Examine.SearchCriteria.ISearchCriteria criteria, ISearcher searchProvider = null) - { - //fixme: pass in the IExamineManager - - if (searchProvider == null) - { - if (!ExamineManager.Instance.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index)) - throw new InvalidOperationException("No index found with name " + Constants.UmbracoIndexes.ExternalIndexName); - searchProvider = index.GetSearcher(); - } - var results = searchProvider.Search(criteria); - return results.ToPublishedSearchResults(UmbracoContext.Current.ContentCache); + return query.Execute().ToPublishedSearchResults(UmbracoContext.Current.ContentCache); } #endregion diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 0864203a50..57e57445ef 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -4,9 +4,7 @@ using System.Linq; using System.Reflection; using System.Xml.XPath; using Examine; -using Examine.LuceneEngine.Providers; -using Examine.LuceneEngine.SearchCriteria; -using Examine.SearchCriteria; +using Examine.Search; using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; @@ -181,16 +179,14 @@ namespace Umbraco.Web #region Search /// - public IEnumerable Search(string term, bool useWildCards = true, string indexName = null) + public IEnumerable Search(string term, string indexName = null) { - return Search(0, 0, out _, term, useWildCards, indexName); + return Search(term, 0, 0, out _, indexName); } /// - public IEnumerable Search(int skip, int take, out long totalRecords, string term, bool useWildCards = true, string indexName = null) + public IEnumerable Search(string term, int skip, int take, out long totalRecords, string indexName = null) { - //fixme: inject IExamineManager - indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; @@ -209,25 +205,17 @@ namespace Umbraco.Web } /// - public IEnumerable Search(ISearchCriteria criteria, ISearcher searchProvider = null) + public IEnumerable Search(IQueryExecutor query) { - return Search(0, 0, out _, criteria, searchProvider); + return Search(query, 0, 0, out _); } /// - public IEnumerable Search(int skip, int take, out long totalRecords, ISearchCriteria criteria, ISearcher searcher = null) + public IEnumerable Search(IQueryExecutor query, int skip, int take, out long totalRecords) { - //fixme: inject IExamineManager - if (searcher == null) - { - if (!ExamineManager.Instance.TryGetIndex(Constants.UmbracoIndexes.ExternalIndexName, out var index)) - throw new InvalidOperationException($"No index found by name {Constants.UmbracoIndexes.ExternalIndexName}"); - searcher = index.GetSearcher(); - } - var results = skip == 0 && take == 0 - ? searcher.Search(criteria) - : searcher.Search(criteria, maxResults: skip + take); + ? query.Execute() + : query.Execute(maxResults: skip + take); totalRecords = results.TotalItemCount; return results.ToPublishedSearchResults(_contentCache); diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 555414da44..71061a5cb3 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -490,9 +490,7 @@ namespace Umbraco.Web.Search while (page * pageSize < total) { //paging with examine, see https://shazwazza.com/post/paging-with-examine/ - var results = searcher.Search( - searcher.CreateCriteria().Field("nodeType", id).Compile(), - maxResults: pageSize * (page + 1)); + var results = searcher.CreateQuery().Field("nodeType", id).Execute(maxResults: pageSize * (page + 1)); total = results.TotalItemCount; var paged = results.Skip(page * pageSize); diff --git a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs index c3ab7318a0..47e73d383c 100644 --- a/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs +++ b/src/Umbraco.Web/Search/UmbracoTreeSearcher.cs @@ -109,11 +109,9 @@ namespace Umbraco.Web.Search return Enumerable.Empty(); } - var raw = internalSearcher.CreateCriteria().RawQuery(sb.ToString()); - - var result = internalSearcher + var result = internalSearcher.CreateQuery().NativeQuery(sb.ToString()) //only return the number of items specified to read up to the amount of records to fill from 0 -> the number of items on the page requested - .Search(raw, Convert.ToInt32(pageSize * (pageIndex + 1))); + .Execute(Convert.ToInt32(pageSize * (pageIndex + 1))); totalFound = result.TotalItemCount; diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index eb6eab9829..9c44539f92 100755 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -62,7 +62,7 @@ - + 2.6.2.25 diff --git a/src/Umbraco.Web/UmbracoHelper.cs b/src/Umbraco.Web/UmbracoHelper.cs index 9698e3e024..b98db65b27 100644 --- a/src/Umbraco.Web/UmbracoHelper.cs +++ b/src/Umbraco.Web/UmbracoHelper.cs @@ -775,62 +775,6 @@ namespace Umbraco.Web #endregion - #region Search - - /// - /// Searches content. - /// - /// - /// - /// - /// - public IEnumerable Search(string term, bool useWildCards = true, string indexName = null) - { - return ContentQuery.Search(term, useWildCards, indexName); - } - - /// - /// Searches content. - /// - /// - /// - /// - /// - /// - /// - /// - public IEnumerable Search(int skip, int take, out long totalRecords, string term, bool useWildCards = true, string indexName = null) - { - return ContentQuery.Search(skip, take, out totalRecords, term, useWildCards, indexName); - } - - /// - /// Searhes content. - /// - /// - /// - /// - /// - /// - /// - public IEnumerable Search(int skip, int take, out long totalRecords, Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) - { - return ContentQuery.Search(skip, take, out totalRecords, criteria, searchProvider); - } - - /// - /// Searhes content. - /// - /// - /// - /// - public IEnumerable Search(Examine.SearchCriteria.ISearchCriteria criteria, Examine.Providers.BaseSearchProvider searchProvider = null) - { - return ContentQuery.Search(criteria, searchProvider); - } - - #endregion - #region Strings /// From 748462689354f3b79cf0d439af3848e17498e296 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 17 Dec 2018 13:11:51 +1100 Subject: [PATCH 21/45] Allows passing in a culture to the IPublishedContentQuery search methods --- src/Umbraco.Examine/IUmbracoIndex.cs | 9 +++- src/Umbraco.Examine/Umbraco.Examine.csproj | 1 - src/Umbraco.Examine/UmbracoExamineIndex.cs | 8 +++ src/Umbraco.Examine/UmbracoExamineSearcher.cs | 52 ------------------- src/Umbraco.Web/IPublishedContentQuery.cs | 4 +- src/Umbraco.Web/PublishedContentQuery.cs | 51 ++++++++++++++---- 6 files changed, 60 insertions(+), 65 deletions(-) delete mode 100644 src/Umbraco.Examine/UmbracoExamineSearcher.cs diff --git a/src/Umbraco.Examine/IUmbracoIndex.cs b/src/Umbraco.Examine/IUmbracoIndex.cs index e70652a342..9461434fff 100644 --- a/src/Umbraco.Examine/IUmbracoIndex.cs +++ b/src/Umbraco.Examine/IUmbracoIndex.cs @@ -1,4 +1,5 @@ -using Examine; +using System.Collections.Generic; +using Examine; namespace Umbraco.Examine { @@ -21,5 +22,11 @@ namespace Umbraco.Examine /// * non-published Variants /// bool PublishedValuesOnly { get; } + + /// + /// Returns a list of all indexed fields + /// + /// + IEnumerable GetFields(); } } diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index a037c3028e..2bdf6f833d 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -88,7 +88,6 @@ - diff --git a/src/Umbraco.Examine/UmbracoExamineIndex.cs b/src/Umbraco.Examine/UmbracoExamineIndex.cs index 1933874fc7..fc7f834a29 100644 --- a/src/Umbraco.Examine/UmbracoExamineIndex.cs +++ b/src/Umbraco.Examine/UmbracoExamineIndex.cs @@ -97,6 +97,14 @@ namespace Umbraco.Examine public bool PublishedValuesOnly { get; protected set; } = false; + /// + public IEnumerable GetFields() + { + //we know this is a LuceneSearcher + var searcher = (LuceneSearcher) GetSearcher(); + return searcher.GetAllIndexedFields(); + } + protected ConfigIndexCriteria ConfigIndexCriteria { get; private set; } /// diff --git a/src/Umbraco.Examine/UmbracoExamineSearcher.cs b/src/Umbraco.Examine/UmbracoExamineSearcher.cs deleted file mode 100644 index 50c6c21e37..0000000000 --- a/src/Umbraco.Examine/UmbracoExamineSearcher.cs +++ /dev/null @@ -1,52 +0,0 @@ -//using System; -//using System.ComponentModel; -//using System.IO; -//using System.Linq; -//using Umbraco.Core; -//using Examine.LuceneEngine.Providers; -//using Lucene.Net.Analysis; -//using Lucene.Net.Index; -//using Umbraco.Core.Composing; -//using Umbraco.Examine.Config; -//using Directory = Lucene.Net.Store.Directory; -//using Examine.LuceneEngine; - -//namespace Umbraco.Examine -//{ -// /// -// /// An Examine searcher which uses Lucene.Net as the -// /// -// public class UmbracoExamineSearcher : LuceneSearcher -// { -// /// -// /// Constructor to allow for creating an indexer at runtime -// /// -// /// -// /// -// /// -// public UmbracoExamineSearcher(string name, Directory luceneDirectory, Analyzer analyzer, FieldValueTypeCollection fieldValueTypeCollection) -// : base(name, luceneDirectory, analyzer, fieldValueTypeCollection) -// { -// } - -// /// -// public UmbracoExamineSearcher(string name, IndexWriter writer, Analyzer analyzer, FieldValueTypeCollection fieldValueTypeCollection) -// : base(name, writer, analyzer, fieldValueTypeCollection) -// { -// } - -// /// -// /// Returns a list of fields to search on, this will also exclude the IndexPathFieldName and node type alias -// /// -// /// -// public override string[] GetAllIndexedFields() -// { -// var fields = base.GetAllIndexedFields(); -// return fields -// .Where(x => x != UmbracoExamineIndexer.IndexPathFieldName) -// .Where(x => x != LuceneIndex.ItemTypeFieldName) -// .ToArray(); -// } - -// } -//} diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index cc388ad094..72ca5d6ddb 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -35,12 +35,12 @@ namespace Umbraco.Web /// /// Searches content. /// - IEnumerable Search(string term, string indexName = null); + IEnumerable Search(string term, string culture = null, string indexName = null); /// /// Searches content. /// - IEnumerable Search(string term, int skip, int take, out long totalRecords, string indexName = null); + IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = null, string indexName = null); /// /// Executes the query and converts the results to PublishedSearchResult. diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 57e57445ef..ea351b5ffd 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using System.Xml.XPath; using Examine; using Examine.Search; @@ -9,6 +10,7 @@ using Umbraco.Core; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.Services; using Umbraco.Core.Xml; +using Umbraco.Examine; using Umbraco.Web.PublishedCache; namespace Umbraco.Web @@ -179,26 +181,49 @@ namespace Umbraco.Web #region Search /// - public IEnumerable Search(string term, string indexName = null) + public IEnumerable Search(string term, string culture = null, string indexName = null) { - return Search(term, 0, 0, out _, indexName); + return Search(term, 0, 0, out _, culture, indexName); } /// - public IEnumerable Search(string term, int skip, int take, out long totalRecords, string indexName = null) + public IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = null, string indexName = null) { indexName = string.IsNullOrEmpty(indexName) ? Constants.UmbracoIndexes.ExternalIndexName : indexName; - if (!ExamineManager.Instance.TryGetIndex(indexName, out var index)) - throw new InvalidOperationException($"No index found by name {indexName}"); + if (!ExamineManager.Instance.TryGetIndex(indexName, out var index) || !(index is IUmbracoIndex umbIndex)) + throw new InvalidOperationException($"No index found by name {indexName} or is not of type {typeof(IUmbracoIndex)}"); - var searcher = index.GetSearcher(); + var searcher = umbIndex.GetSearcher(); - var results = skip == 0 && take == 0 - ? searcher.Search(term) - : searcher.Search(term, maxResults: skip + take); + ISearchResults results; + if (!culture.IsNullOrWhiteSpace()) + { + results = searcher.Search(term, (skip == 0 && take == 0 + ? 500 //default max results + : skip + take)); + } + else + { + //get all index fields suffixed with the culture name supplied + var cultureFields = new List(); + var fields = umbIndex.GetFields(); + var qry = searcher.CreateQuery().Field(UmbracoContentIndex.VariesByCultureFieldName, 1); //must vary by culture + // ReSharper disable once LoopCanBeConvertedToQuery + foreach (var field in fields) + { + var match = CultureIsoCodeFieldName.Match(field); + if (match.Success && match.Groups.Count == 2 && culture.InvariantEquals(match.Groups[1].Value)) + cultureFields.Add(field); + } + + results = qry.And().ManagedQuery(term, cultureFields.ToArray()).Execute((skip == 0 && take == 0 + ? 500 //default max results + : skip + take)); + + } totalRecords = results.TotalItemCount; return results.ToPublishedSearchResults(_contentCache); @@ -221,6 +246,14 @@ namespace Umbraco.Web return results.ToPublishedSearchResults(_contentCache); } + /// + /// Matches a culture iso name suffix + /// + /// + /// myFieldName_en-us will match the "en-us" + /// + private static readonly Regex CultureIsoCodeFieldName = new Regex("^[_\\w]+_([a-z]{2}-[a-z0-9]{2,4})$", RegexOptions.Compiled); + #endregion } From 07d23208982a7906bfb6becfc1d040767e4bf376 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 17 Dec 2018 13:44:11 +1100 Subject: [PATCH 22/45] fixes bool --- src/Umbraco.Web/PublishedContentQuery.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index ea351b5ffd..79f6e07282 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -199,7 +199,7 @@ namespace Umbraco.Web var searcher = umbIndex.GetSearcher(); ISearchResults results; - if (!culture.IsNullOrWhiteSpace()) + if (culture.IsNullOrWhiteSpace()) { results = searcher.Search(term, (skip == 0 && take == 0 ? 500 //default max results From 150ab1366863e1fd7456346c67c9c8dd4d1c433c Mon Sep 17 00:00:00 2001 From: Marc Goodson Date: Thu, 15 Nov 2018 20:13:52 +0000 Subject: [PATCH 23/45] Honour 'Order Direction' passed into GetPagedResultsByQuery Wanting to update the MiniListView so that you can change the sort order of the results, so that the most recent items are first, eg sort by descending, however chasing this through to the Entity Repository GetPagedResultsByQuery although the sort direction is passed at each stage, inside this method it is ignored... is this the best way to add it back in? --- .../Persistence/Repositories/EntityRepository.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs index adfce25cce..f28b226932 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; @@ -64,7 +64,8 @@ namespace Umbraco.Core.Persistence.Repositories }, objectTypeId); var translator = new SqlTranslator(sqlClause, query); var entitySql = translator.Translate(); - var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)).OrderBy("umbracoNode.id"); + var pagedSql = entitySql.Append(GetGroupBy(isContent, isMedia, false)); + pagedSql = (orderDirection == Direction.Descending) ? pagedSql.OrderByDescending("umbracoNode.id") : pagedSql.OrderBy("umbracoNode.id"); IEnumerable result; @@ -80,8 +81,8 @@ namespace Umbraco.Core.Persistence.Repositories foreach (var idGroup in ids) { var propSql = GetPropertySql(Constants.ObjectTypes.Media) - .Where("contentNodeId IN (@ids)", new {ids = idGroup}) - .OrderBy("contentNodeId"); + .Where("contentNodeId IN (@ids)", new { ids = idGroup }); + propSql = (orderDirection == Direction.Descending) ? propSql.OrderByDescending("contentNodeId") : propSql.OrderBy("contentNodeId"); //This does NOT fetch all data into memory in a list, this will read // over the records as a data reader, this is much better for performance and memory, From 08dc5a40381a6a1e6b27bb22844a196ba294a08b Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 3 Dec 2018 13:20:50 +0100 Subject: [PATCH 24/45] 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 25/45] 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 26/45] Fix "toggle all editors" for grid rows --- .../grid/dialogs/rowconfig.controller.js | 20 +++++++++---------- .../grid/dialogs/rowconfig.html | 3 +-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js index 3cd16301cd..4e3dde50e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js @@ -22,16 +22,6 @@ function RowConfigController($scope) { return ((spans / $scope.columns) * 100).toFixed(8); }; - $scope.toggleCollection = function(collection, toggle) { - if (toggle) { - collection = []; - } - else { - delete collection; - } - }; - - /**************** area *****************/ @@ -55,9 +45,19 @@ function RowConfigController($scope) { row.areas.push(cell); } $scope.currentCell = cell; + $scope.currentCell.allowAll = cell.allowAll || !cell.allowed || !cell.allowed.length; } }; + $scope.toggleAllowed = function (cell) { + if (cell.allowed) { + delete cell.allowed; + } + else { + cell.allowed = []; + } + } + $scope.deleteArea = function (cell, row) { if ($scope.currentCell === cell) { $scope.currentCell = undefined; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html index 542ef7da86..8ae229d048 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.html @@ -72,8 +72,7 @@ From f3e8b70b8cf816a8030da0913f1af05500ddf86a Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 30 Nov 2018 15:07:48 +0100 Subject: [PATCH 27/45] Show only the success message upon successful update of notifications --- src/Umbraco.Web.UI.Client/src/views/content/notify.html | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/notify.html b/src/Umbraco.Web.UI.Client/src/views/content/notify.html index cb12d92f08..e7c4d4d785 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/notify.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/notify.html @@ -15,10 +15,11 @@
{{currentNode.name}}
+ -
+
-
Set your notification for {{ currentNode.name }}
+ Set your notification for {{ currentNode.name }}
- -
    -
  • +
    +
      +
    • + + {{group.containerPath}} +
    • +
    • -
      - -
      +
      + +
      - - -
    • -
    + +
  • +
+
diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index 1d728c63a5..5c75bc07f7 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -109,6 +109,25 @@ namespace Umbraco.Web.Editors var availableCompositions = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes); + Func> getEntityContainers = contentType => + { + if (contentType == null) + { + return null; + } + switch (type) + { + case UmbracoObjectTypes.DocumentType: + return Services.ContentTypeService.GetContentTypeContainers(contentType as IContentType); + case UmbracoObjectTypes.MediaType: + return Services.ContentTypeService.GetMediaTypeContainers(contentType as IMediaType); + case UmbracoObjectTypes.MemberType: + return new EntityContainer[0]; + default: + throw new ArgumentOutOfRangeException("The entity type was not a content type"); + } + }; + var currCompositions = source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray(); var compAliases = currCompositions.Select(x => x.Alias).ToArray(); var ancestors = availableCompositions.Ancestors.Select(x => x.Alias); @@ -117,9 +136,6 @@ namespace Umbraco.Web.Editors .Select(x => new Tuple(Mapper.Map(x.Composition), x.Allowed)) .Select(x => { - //translate the name - x.Item1.Name = TranslateItem(x.Item1.Name); - //we need to ensure that the item is enabled if it is already selected // but do not allow it if it is any of the ancestors if (compAliases.Contains(x.Item1.Alias) && ancestors.Contains(x.Item1.Alias) == false) @@ -128,6 +144,14 @@ namespace Umbraco.Web.Editors x = new Tuple(x.Item1, true); } + //translate the name + x.Item1.Name = TranslateItem(x.Item1.Name); + + var contentType = allContentTypes.FirstOrDefault(c => c.Key == x.Item1.Key); + var containers = getEntityContainers(contentType)?.ToArray(); + var containerPath = $"/{(containers != null && containers.Any() ? $"{string.Join("/", containers.Select(c => c.Name))}/" : null)}"; + x.Item1.AdditionalData["containerPath"] = containerPath; + return x; }) .ToList(); From d01dfb4ba67e0c5ac46e9bf65d4e8dcd724531ce Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 29 Nov 2018 16:45:39 +0100 Subject: [PATCH 35/45] Add search to move media dialog --- .../src/views/media/media.move.controller.js | 33 +++++++++++++++-- .../src/views/media/move.html | 37 ++++++++++++++----- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js index 77e59cbbfa..9213337a0d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js @@ -6,6 +6,14 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", $scope.dialogTreeEventHandler = $({}); var node = dialogOptions.currentNode; + $scope.busy = false; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + } $scope.treeModel = { hideHeader: false } @@ -44,9 +52,24 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", } } - $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); - $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); - $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { event: evt, node: result }); + }; + + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; $scope.move = function () { $scope.busy = true; @@ -79,6 +102,10 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", }); }; + $scope.dialogTreeEventHandler.bind("treeLoaded", treeLoadedHandler); + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + $scope.$on('$destroy', function () { $scope.dialogTreeEventHandler.unbind("treeLoaded", treeLoadedHandler); $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); diff --git a/src/Umbraco.Web.UI.Client/src/views/media/move.html b/src/Umbraco.Web.UI.Client/src/views/media/move.html index 88c64f1992..b76f9a6fb2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/move.html +++ b/src/Umbraco.Web.UI.Client/src/views/media/move.html @@ -25,15 +25,34 @@
- - + + + +
+ + + + +
+ + +
Date: Wed, 19 Dec 2018 18:19:51 +0100 Subject: [PATCH 36/45] Add "Move" option for deleted items in the content tree (#3772) --- .../content/content.restore.controller.js | 133 ++++++++++++++---- .../src/views/content/restore.html | 92 ++++++++++-- src/Umbraco.Web.UI/umbraco/config/lang/da.xml | 6 +- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 4 +- .../umbraco/config/lang/en_us.xml | 4 +- .../Trees/ContentTreeController.cs | 1 + 6 files changed, 189 insertions(+), 51 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js index 62eaa6fca6..cb6a85fbb1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js @@ -1,22 +1,95 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController", - function ($scope, relationResource, contentResource, navigationService, appState, treeService, localizationService) { + function ($scope, relationResource, contentResource, navigationService, appState, treeService, userService) { var dialogOptions = $scope.dialogOptions; - var node = dialogOptions.currentNode; + $scope.source = _.clone(dialogOptions.currentNode); - $scope.error = null; + $scope.error = null; + $scope.loading = true; + $scope.moving = false; $scope.success = false; - relationResource.getByChildId(node.id, "relateParentDocumentOnDelete").then(function (data) { + $scope.dialogTreeEventHandler = $({}); + $scope.searchInfo = { + showSearch: false, + results: [], + selectedSearchResults: [] + } + $scope.treeModel = { + hideHeader: false + } + userService.getCurrentUser().then(function (userData) { + $scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1; + }); - if (data.length == 0) { - $scope.success = false; - $scope.error = { - errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), - data: { - Message: localizationService.localize('recycleBin_noRestoreRelation') - } - } + function nodeSelectHandler(ev, args) { + + if (args && args.event) { + args.event.preventDefault(); + args.event.stopPropagation(); + } + + if ($scope.target) { + //un-select if there's a current one selected + $scope.target.selected = false; + } + + $scope.target = args.node; + $scope.target.selected = true; + + } + + function nodeExpandedHandler(ev, args) { + // open mini list view for list views + if (args.node.metaData.isContainer) { + openMiniListView(args.node); + } + } + + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.results = []; + } + + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler(evt, { event: evt, node: result }); + }; + + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; + + $scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler); + + $scope.$on('$destroy', function () { + $scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler); + $scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler); + }); + + // Mini list view + $scope.selectListViewNode = function (node) { + node.selected = node.selected === true ? false : true; + nodeSelectHandler({}, { node: node }); + }; + + $scope.closeMiniListView = function () { + $scope.miniListView = undefined; + }; + + function openMiniListView(node) { + $scope.miniListView = node; + } + + relationResource.getByChildId($scope.source.id, "relateParentDocumentOnDelete").then(function (data) { + $scope.loading = false; + + if (!data.length) { + $scope.moving = true; return; } @@ -25,39 +98,37 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController" if ($scope.relation.parentId == -1) { $scope.target = { id: -1, name: "Root" }; - } else { - contentResource.getById($scope.relation.parentId).then(function (data) { - $scope.target = data; + } else { + $scope.loading = true; + + contentResource.getById($scope.relation.parentId).then(function (data) { + $scope.loading = false; + $scope.target = data; // make sure the target item isn't in the recycle bin - if($scope.target.path.indexOf("-20") !== -1) { - $scope.error = { - errorMsg: localizationService.localize('recycleBin_itemCannotBeRestored'), - data: { - Message: localizationService.localize('recycleBin_restoreUnderRecycled').then(function (value) { - value.replace('%0%', $scope.target.name); - }) - } - }; - $scope.success = false; - } - + if ($scope.target.path.indexOf("-20") !== -1) { + $scope.moving = true; + $scope.target = null; + } }, function (err) { - $scope.success = false; + $scope.loading = false; $scope.error = err; }); } }, function (err) { - $scope.success = false; + $scope.loading = false; $scope.error = err; }); $scope.restore = function () { + $scope.loading = true; + // this code was copied from `content.move.controller.js` - contentResource.move({ parentId: $scope.target.id, id: node.id }) + contentResource.move({ parentId: $scope.target.id, id: $scope.source.id }) .then(function (path) { + $scope.loading = false; $scope.success = true; //first we need to remove the node that launched the dialog @@ -78,7 +149,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.RestoreController" }); }, function (err) { - $scope.success = false; + $scope.loading = false; $scope.error = err; }); }; diff --git a/src/Umbraco.Web.UI.Client/src/views/content/restore.html b/src/Umbraco.Web.UI.Client/src/views/content/restore.html index e99e2eb251..3218b8f1b5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/restore.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/restore.html @@ -1,26 +1,92 @@
-
+
+ + -

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

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

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

- -
+
+ +

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

+ +
+ +
+
+
+
Cannot automatically restore this item
+
There is no location where this item can be automatically restored. You can move the item manually using the tree below.
+
+
+ +
+ + + +
+ + + + +
+ + +
+
+ + + + +
- diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml index 92a38692a2..4f61bf3ee4 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/da.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/da.xml @@ -1622,8 +1622,8 @@ Mange hilsner fra Umbraco robotten Slettet indhold med Id: {0} Relateret til original "parent" med id: {1} Slettet medie med Id: {0} relateret til original "parent" / mappe med id: {1} - Kan ikke automatisk genoprette dette dokument/medie - Der findes ikke nogen "Genopret" relation for dette dokument/medie. Brug "Flyt" muligheden fra menuen for at flytte det manuelt. - Det dokument/medie du ønsker at genoprette under ('%0%') er i skraldespanden. Brug "Flyt" muligheden fra menuen for at flytte det manuelt. + Kan ikke automatisk genoprette dette element + Der er ikke nogen placering hvor dette element automatisk kan genoprettes. Du kan flytte elementet manuelt i træet nedenfor. + blev genoprettet under diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 5a2a28f549..76cf013b40 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -2224,7 +2224,7 @@ To manage your website, simply open the Umbraco back office and start adding con Trashed content with Id: {0} related to original parent content with Id: {1} Trashed media with Id: {0} related to original parent media item with Id: {1} Cannot automatically restore this item - There is no 'restore' relation found for this node. Use the Move menu item to move it manually. - The item you want to restore it under ('%0%') is in the recycle bin. Use the Move menu item to move the item manually. + There is no location where this item can be automatically restored. You can move the item manually using the tree below. + was restored under diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index fc5ae4428f..47fef59019 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -2217,8 +2217,8 @@ To manage your website, simply open the Umbraco back office and start adding con Trashed content with Id: {0} related to original parent content with Id: {1} Trashed media with Id: {0} related to original parent media item with Id: {1} Cannot automatically restore this item - There is no 'restore' relation found for this node. Use the Move menu item to move it manually. - The item you want to restore it under ('%0%') is in the recycle bin. Use the Move menu item to move the item manually. + There is no location where this item can be automatically restored. You can move the item manually using the tree below. + was restored under Select your notifications for diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index c3fe7df483..293cc3d36a 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -255,6 +255,7 @@ namespace Umbraco.Web.Trees { var menu = new MenuItemCollection(); menu.Items.Add(ui.Text("actions", ActionRestore.Instance.Alias)); + menu.Items.Add(ui.Text("actions", ActionMove.Instance.Alias)); menu.Items.Add(ui.Text("actions", ActionDelete.Instance.Alias)); menu.Items.Add(ui.Text("actions", ActionRefresh.Instance.Alias), true); From e7a06467f16da3682b01b160dc83c6b0fbefd9bc Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 20 Dec 2018 17:50:16 +1100 Subject: [PATCH 37/45] Fixes merge issues, manually merges other changes --- .../src/common/services/navigation.service.js | 8 +--- .../content/content.delete.controller.js | 21 ++++++---- .../views/media/media.delete.controller.js | 10 ++++- .../src/views/media/media.move.controller.js | 30 +++++++++++++- src/Umbraco.Web.UI/Umbraco/config/lang/en.xml | 2 +- .../Umbraco/config/lang/en_us.xml | 4 +- .../Editors/ContentTypeControllerBase.cs | 41 ++++++++++--------- 7 files changed, 74 insertions(+), 42 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 23f3fb6d58..ecc92263bc 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 @@ -30,12 +30,6 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve var nonRoutingQueryStrings = ["mculture", "cculture"]; var retainedQueryStrings = ['mculture']; - //used to track the current dialog object - var currentDialog = null; - - //tracks the user profile dialog - var userDialog = null; - function setMode(mode) { switch (mode) { case 'tree': @@ -287,7 +281,7 @@ function navigationService($routeParams, $location, $q, $timeout, $injector, eve appState.setGlobalState("showTray", false); }, - /** + /** * @ngdoc method * @name umbraco.services.navigationService#syncTree * @methodOf umbraco.services.navigationService 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 d4be18cf05..92e02d0d14 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 @@ -34,22 +34,25 @@ function ContentDeleteController($scope, $timeout, contentResource, treeService, toggleDeleting(false); if (rootNode) { - $timeout(function () { - //ensure the recycle bin has child nodes now - var recycleBin = treeService.getDescendantNode(rootNode, -20); - if (recycleBin) { - //TODO: This seems to return a rejection and we end up with "Possibly unhanded rejection" - treeService.syncTree({ node: recycleBin, path: treeService.getPath(recycleBin), forceReload: true }); + //ensure the recycle bin has child nodes now + 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" }); } - }, 500); + } } - + //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) { //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 974f2ad21b..e5e95e94df 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" }); + } } } @@ -37,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); diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js index 8bb1dc3ac8..163cee9088 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js @@ -5,6 +5,14 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", $scope.dialogTreeApi = {}; $scope.source = _.clone($scope.currentNode); + $scope.busy = false; + $scope.searchInfo = { + searchFromId: null, + searchFromName: null, + showSearch: false, + results: [], + selectedSearchResults: [] + } $scope.treeModel = { hideHeader: false } @@ -52,6 +60,24 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", $scope.close = function() { navigationService.hideDialog(); }; + $scope.hideSearch = function () { + $scope.searchInfo.showSearch = false; + $scope.searchInfo.searchFromId = null; + $scope.searchInfo.searchFromName = null; + $scope.searchInfo.results = []; + } + + // method to select a search result + $scope.selectResult = function (evt, result) { + result.selected = result.selected === true ? false : true; + nodeSelectHandler({ event: evt, node: result }); + }; + + //callback when there are search results + $scope.onSearchResults = function (results) { + $scope.searchInfo.results = results; + $scope.searchInfo.showSearch = true; + }; $scope.move = function () { $scope.busy = true; @@ -83,11 +109,11 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.MoveController", $scope.error = err; }); }; - + // Mini list view $scope.selectListViewNode = function (node) { node.selected = node.selected === true ? false : true; - nodeSelectHandler({}, { node: node }); + nodeSelectHandler({ node: node }); }; $scope.closeMiniListView = function () { diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml index a1ab9c6e6c..08d286d41d 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en.xml @@ -1706,7 +1706,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 Change Your profile 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 4bc117e574..a3b5185613 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -289,7 +289,7 @@ Click to upload or click here to choose files - You can drag files here to upload + You can drag files here to upload. Cannot upload this file, it does not have an approved file type Max file size is Media root @@ -1754,7 +1754,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 Change Your profile diff --git a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs index cc2c1ab622..2572bed816 100644 --- a/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs +++ b/src/Umbraco.Web/Editors/ContentTypeControllerBase.cs @@ -89,24 +89,7 @@ namespace Umbraco.Web.Editors var availableCompositions = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes); - Func> getEntityContainers = contentType => - { - if (contentType == null) - { - return null; - } - switch (type) - { - case UmbracoObjectTypes.DocumentType: - return Services.ContentTypeService.GetContentTypeContainers(contentType as IContentType); - case UmbracoObjectTypes.MediaType: - return Services.ContentTypeService.GetMediaTypeContainers(contentType as IMediaType); - case UmbracoObjectTypes.MemberType: - return new EntityContainer[0]; - default: - throw new ArgumentOutOfRangeException("The entity type was not a content type"); - } - }; + var currCompositions = source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray(); var compAliases = currCompositions.Select(x => x.Alias).ToArray(); @@ -128,7 +111,7 @@ namespace Umbraco.Web.Editors x.Item1.Name = TranslateItem(x.Item1.Name); var contentType = allContentTypes.FirstOrDefault(c => c.Key == x.Item1.Key); - var containers = getEntityContainers(contentType)?.ToArray(); + var containers = GetEntityContainers(contentType, type)?.ToArray(); var containerPath = $"/{(containers != null && containers.Any() ? $"{string.Join("/", containers.Select(c => c.Name))}/" : null)}"; x.Item1.AdditionalData["containerPath"] = containerPath; @@ -137,6 +120,26 @@ namespace Umbraco.Web.Editors .ToList(); } + private IEnumerable GetEntityContainers(IContentTypeComposition contentType, UmbracoObjectTypes type) + { + if (contentType == null) + { + return null; + } + + switch (type) + { + case UmbracoObjectTypes.DocumentType: + return Services.ContentTypeService.GetContainers(contentType as IContentType); + case UmbracoObjectTypes.MediaType: + return Services.MediaTypeService.GetContainers(contentType as IMediaType); + case UmbracoObjectTypes.MemberType: + return new EntityContainer[0]; + default: + throw new ArgumentOutOfRangeException("The entity type was not a content type"); + } + } + /// /// Returns a list of content types where a particular composition content type is used /// From 23eabee1a263c30301a65c4e29666059216934c2 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 20 Dec 2018 13:29:57 +0100 Subject: [PATCH 38/45] Misc fixes --- src/Umbraco.Web/IPublishedContentQuery.cs | 15 +++++++++++++++ src/Umbraco.Web/PublishedContentQuery.cs | 13 ++++++------- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/IPublishedContentQuery.cs b/src/Umbraco.Web/IPublishedContentQuery.cs index 72ca5d6ddb..c3a065b9dc 100644 --- a/src/Umbraco.Web/IPublishedContentQuery.cs +++ b/src/Umbraco.Web/IPublishedContentQuery.cs @@ -35,11 +35,26 @@ namespace Umbraco.Web /// /// Searches content. /// + /// Term to search. + /// Optional culture. + /// Optional index name. + /// + /// When the is not specified, all cultures are searched. + /// IEnumerable Search(string term, string culture = null, string indexName = null); /// /// Searches content. /// + /// Term to search. + /// Numbers of items to skip. + /// Numbers of items to return. + /// Total number of matching items. + /// Optional culture. + /// Optional index name. + /// + /// When the is not specified, all cultures are searched. + /// IEnumerable Search(string term, int skip, int take, out long totalRecords, string culture = null, string indexName = null); /// diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 79f6e07282..3d8f36ec1a 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -198,12 +198,13 @@ namespace Umbraco.Web var searcher = umbIndex.GetSearcher(); + // default to max 500 results + var count = skip == 0 && take == 0 ? 500 : skip + take; + ISearchResults results; if (culture.IsNullOrWhiteSpace()) { - results = searcher.Search(term, (skip == 0 && take == 0 - ? 500 //default max results - : skip + take)); + results = searcher.Search(term, count); } else { @@ -219,10 +220,8 @@ namespace Umbraco.Web cultureFields.Add(field); } - results = qry.And().ManagedQuery(term, cultureFields.ToArray()).Execute((skip == 0 && take == 0 - ? 500 //default max results - : skip + take)); - + qry = qry.And().ManagedQuery(term, cultureFields.ToArray()); + results = qry.Execute(count); } totalRecords = results.TotalItemCount; From 7e20e81cc8de8a69ece510f3681755e1ed53939c Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Dec 2018 13:15:46 +1100 Subject: [PATCH 39/45] Fixes EntityRepository and EntityService to perform order by operations correctly --- .../Repositories/IEntityRepository.cs | 3 +- .../Implement/EntityRepository.cs | 68 +++++++++++----- src/Umbraco.Core/Services/IEntityService.cs | 11 +-- .../Services/Implement/EntityService.cs | 20 ++--- .../Services/EntityServiceTests.cs | 80 ++++++++++++++++++- src/Umbraco.Web/Editors/ContentController.cs | 2 +- src/Umbraco.Web/Editors/EntityController.cs | 17 +++- .../Trees/ContentTypeTreeController.cs | 4 +- .../Trees/DataTypeTreeController.cs | 4 +- .../Trees/MediaTypeTreeController.cs | 5 +- .../Trees/TemplatesTreeController.cs | 3 +- 11 files changed, 164 insertions(+), 53 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs index b53b117a1a..69f6ef4c5f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IEntityRepository.cs @@ -4,6 +4,7 @@ using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Querying; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -30,6 +31,6 @@ namespace Umbraco.Core.Persistence.Repositories bool Exists(Guid key); IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, IQuery filter = null); + IQuery filter, Ordering ordering); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs index 8d6f67e9db..2be27deb0a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/EntityRepository.cs @@ -10,6 +10,7 @@ using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; using Umbraco.Core.Persistence.SqlSyntax; +using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -25,12 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement internal class EntityRepository : IEntityRepository { private readonly IScopeAccessor _scopeAccessor; - private readonly ILanguageRepository _langRepository; - public EntityRepository(IScopeAccessor scopeAccessor, ILanguageRepository langRepository) + public EntityRepository(IScopeAccessor scopeAccessor) { _scopeAccessor = scopeAccessor; - _langRepository = langRepository; } protected IUmbracoDatabase Database => _scopeAccessor.AmbientScope.Database; @@ -41,7 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // get a page of entities public IEnumerable GetPagedResultsByQuery(IQuery query, Guid objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy, Direction orderDirection, IQuery filter = null) + IQuery filter, Ordering ordering) { var isContent = objectType == Constants.ObjectTypes.Document || objectType == Constants.ObjectTypes.DocumentBlueprint; var isMedia = objectType == Constants.ObjectTypes.Media; @@ -53,11 +52,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement x.Where(filterClause.Item1, filterClause.Item2); }, objectType); + ordering = ordering ?? Ordering.ByDefault(); + var translator = new SqlTranslator(sql, query); sql = translator.Translate(); - sql = AddGroupBy(isContent, isMedia, sql); + sql = AddGroupBy(isContent, isMedia, sql, ordering.IsEmpty); + + if (!ordering.IsEmpty) + { + // apply ordering + ApplyOrdering(ref sql, ordering); + } + //fixme - we should be able to do sql = sql.OrderBy(x => Alias(x.NodeId, "NodeId")); but we can't because the OrderBy extension don't support Alias currently - sql = sql.OrderBy("NodeId"); + //no matter what we always must have node id ordered at the end + sql = ordering.Direction == Direction.Ascending ? sql.OrderBy("NodeId") : sql.OrderByDescending("NodeId"); var page = Database.Page(pageIndex + 1, pageSize, sql); var dtos = page.Items; @@ -81,6 +90,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return dto == null ? null : BuildEntity(false, false, dto); } + private IEntitySlim GetEntity(Sql sql, bool isContent, bool isMedia) { //isContent is going to return a 1:M result now with the variants so we need to do different things @@ -200,7 +210,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var sqlClause = GetBase(false, false, null); var translator = new SqlTranslator(sqlClause, query); var sql = translator.Translate(); - sql = AddGroupBy(false, false, sql); + sql = AddGroupBy(false, false, sql, true); var dtos = Database.Fetch(sql); return dtos.Select(x => BuildEntity(false, false, x)).ToList(); } @@ -214,7 +224,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var translator = new SqlTranslator(sql, query); sql = translator.Translate(); - sql = AddGroupBy(isContent, isMedia, sql); + sql = AddGroupBy(isContent, isMedia, sql, true); return GetEntities(sql, isContent, isMedia, true); } @@ -229,7 +239,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var translator = new SqlTranslator(sql, query); sql = translator.Translate(); - sql = AddGroupBy(isContent, isMedia, sql); + sql = AddGroupBy(isContent, isMedia, sql, true); return GetEntities(sql, isContent, isMedia, false); } @@ -361,21 +371,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Guid uniqueId) { var sql = GetBaseWhere(isContent, isMedia, false, objectType, uniqueId); - return AddGroupBy(isContent, isMedia, sql); + return AddGroupBy(isContent, isMedia, sql, true); } // gets the full sql for a given object type and a given node id protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, int nodeId) { var sql = GetBaseWhere(isContent, isMedia, false, objectType, nodeId); - return AddGroupBy(isContent, isMedia, sql); + return AddGroupBy(isContent, isMedia, sql, true); } // gets the full sql for a given object type, with a given filter protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectType, Action> filter) { var sql = GetBaseWhere(isContent, isMedia, false, filter, objectType); - return AddGroupBy(isContent, isMedia, sql); + return AddGroupBy(isContent, isMedia, sql, true); } private Sql GetPropertyData(int versionId) @@ -401,7 +411,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets the base SELECT + FROM [+ filter] sql // always from the 'current' content version - protected virtual Sql GetBase(bool isContent, bool isMedia, Action> filter, bool isCount = false) + protected Sql GetBase(bool isContent, bool isMedia, Action> filter, bool isCount = false) { var sql = Sql(); @@ -460,7 +470,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets the base SELECT + FROM [+ filter] + WHERE sql // for a given object type, with a given filter - protected virtual Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Action> filter, Guid objectType) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Action> filter, Guid objectType) { return GetBase(isContent, isMedia, filter, isCount) .Where(x => x.NodeObjectType == objectType); @@ -468,25 +478,25 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets the base SELECT + FROM + WHERE sql // for a given node id - protected virtual Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, int id) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, int id) { var sql = GetBase(isContent, isMedia, null, isCount) .Where(x => x.NodeId == id); - return AddGroupBy(isContent, isMedia, sql); + return AddGroupBy(isContent, isMedia, sql, true); } // gets the base SELECT + FROM + WHERE sql // for a given unique id - protected virtual Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid uniqueId) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid uniqueId) { var sql = GetBase(isContent, isMedia, null, isCount) .Where(x => x.UniqueId == uniqueId); - return AddGroupBy(isContent, isMedia, sql); + return AddGroupBy(isContent, isMedia, sql, true); } // gets the base SELECT + FROM + WHERE sql // for a given object type and node id - protected virtual Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, int nodeId) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, int nodeId) { return GetBase(isContent, isMedia, null, isCount) .Where(x => x.NodeId == nodeId && x.NodeObjectType == objectType); @@ -494,7 +504,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets the base SELECT + FROM + WHERE sql // for a given object type and unique id - protected virtual Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, Guid uniqueId) + protected Sql GetBaseWhere(bool isContent, bool isMedia, bool isCount, Guid objectType, Guid uniqueId) { return GetBase(isContent, isMedia, null, isCount) .Where(x => x.UniqueId == uniqueId && x.NodeObjectType == objectType); @@ -502,7 +512,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets the GROUP BY / ORDER BY sql // required in order to count children - protected virtual Sql AddGroupBy(bool isContent, bool isMedia, Sql sql, bool sort = true) + protected Sql AddGroupBy(bool isContent, bool isMedia, Sql sql, bool defaultSort) { sql .GroupBy(x => x.NodeId, x => x.Trashed, x => x.ParentId, x => x.UserId, x => x.Level, x => x.Path) @@ -520,12 +530,26 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .AndBy(x => x.Id) .AndBy(x => x.Alias, x => x.Icon, x => x.Thumbnail, x => x.IsContainer, x => x.Variations); - if (sort) + if (defaultSort) sql.OrderBy(x => x.SortOrder); return sql; } + private void ApplyOrdering(ref Sql sql, Ordering ordering) + { + if (sql == null) throw new ArgumentNullException(nameof(sql)); + if (ordering == null) throw new ArgumentNullException(nameof(ordering)); + + //fixme - although this works for name, it probably doesn't work for others without an alias of some sort + var orderBy = ordering.OrderBy; + + if (ordering.Direction == Direction.Ascending) + sql.OrderBy(orderBy); + else + sql.OrderByDescending(orderBy); + } + #endregion #region Classes diff --git a/src/Umbraco.Core/Services/IEntityService.cs b/src/Umbraco.Core/Services/IEntityService.cs index 926afcf0a9..3937d5bf40 100644 --- a/src/Umbraco.Core/Services/IEntityService.cs +++ b/src/Umbraco.Core/Services/IEntityService.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; using Umbraco.Core.Persistence.DatabaseModelDefinitions; +using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Services { @@ -227,25 +228,25 @@ namespace Umbraco.Core.Services /// Gets children of an entity. /// IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = ""); + IQuery filter = null, Ordering ordering = null); /// /// Gets descendants of an entity. /// IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + IQuery filter = null, Ordering ordering = null); /// /// Gets descendants of entities. /// IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = ""); + IQuery filter = null, Ordering ordering = null); /// - /// Gets descendants of root. + /// Gets descendants of root. fixme: Do we really need this? why not just pass in -1 /// IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true); + IQuery filter = null, Ordering ordering = null, bool includeTrashed = true); /// /// Gets the object type of an entity. diff --git a/src/Umbraco.Core/Services/Implement/EntityService.cs b/src/Umbraco.Core/Services/Implement/EntityService.cs index 45c229214a..4a3db29940 100644 --- a/src/Umbraco.Core/Services/Implement/EntityService.cs +++ b/src/Umbraco.Core/Services/Implement/EntityService.cs @@ -415,20 +415,19 @@ namespace Umbraco.Core.Services.Implement /// public IEnumerable GetPagedChildren(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "SortOrder", Direction orderDirection = Direction.Ascending, string filter = "") + IQuery filter = null, Ordering ordering = null) { using (ScopeProvider.CreateScope(autoComplete: true)) { var query = Query().Where(x => x.ParentId == id && x.Trashed == false); - var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query().Where(x => x.Name.Contains(filter)); - return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering); } } /// public IEnumerable GetPagedDescendants(int id, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + IQuery filter = null, Ordering ordering = null) { using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -448,14 +447,13 @@ namespace Umbraco.Core.Services.Implement query.Where(x => x.Path.SqlStartsWith(path + ",", TextColumnType.NVarchar)); } - var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query().Where(x => x.Name.Contains(filter)); - return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, filter, ordering); } } /// public IEnumerable GetPagedDescendants(IEnumerable ids, UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "") + IQuery filter = null, Ordering ordering = null) { totalRecords = 0; @@ -492,14 +490,13 @@ namespace Umbraco.Core.Services.Implement query.WhereAny(clauses); } - var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query().Where(x => x.Name.Contains(filter)); - return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return _entityRepository.GetPagedResultsByQuery(query, objectTypeGuid, pageIndex, pageSize, out totalRecords, filter, ordering); } } /// public IEnumerable GetPagedDescendants(UmbracoObjectTypes objectType, long pageIndex, int pageSize, out long totalRecords, - string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "", bool includeTrashed = true) + IQuery filter = null, Ordering ordering = null, bool includeTrashed = true) { using (ScopeProvider.CreateScope(autoComplete: true)) { @@ -507,8 +504,7 @@ namespace Umbraco.Core.Services.Implement if (includeTrashed == false) query.Where(x => x.Trashed == false); - var filterQuery = string.IsNullOrWhiteSpace(filter) ? null : Query().Where(x => x.Name.Contains(filter)); - return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, orderBy, orderDirection, filterQuery); + return _entityRepository.GetPagedResultsByQuery(query, objectType.GetGuid(), pageIndex, pageSize, out totalRecords, filter, ordering); } } diff --git a/src/Umbraco.Tests/Services/EntityServiceTests.cs b/src/Umbraco.Tests/Services/EntityServiceTests.cs index 1db653f4ab..2425f8b74a 100644 --- a/src/Umbraco.Tests/Services/EntityServiceTests.cs +++ b/src/Umbraco.Tests/Services/EntityServiceTests.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; @@ -37,6 +38,53 @@ namespace Umbraco.Tests.Services } } + [Test] + public void EntityService_Can_Get_Paged_Descendants_Ordering_Path() + { + + var contentType = ServiceContext.ContentTypeService.Get("umbTextpage"); + + var root = MockedContent.CreateSimpleContent(contentType); + ServiceContext.ContentService.Save(root); + var rootId = root.Id; + var ids = new List(); + for (int i = 0; i < 10; i++) + { + var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); + ServiceContext.ContentService.Save(c1); + ids.Add(c1.Id); + root = c1; // make a hierarchy + } + + var service = ServiceContext.EntityService; + + long total; + + var entities = service.GetPagedDescendants(rootId, UmbracoObjectTypes.Document, 0, 6, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(6)); + Assert.That(total, Is.EqualTo(10)); + Assert.AreEqual(ids[0], entities[0].Id); + + entities = service.GetPagedDescendants(rootId, UmbracoObjectTypes.Document, 1, 6, out total).ToArray(); + Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(total, Is.EqualTo(10)); + Assert.AreEqual(ids[6], entities[0].Id); + + //Test ordering direction + + entities = service.GetPagedDescendants(rootId, UmbracoObjectTypes.Document, 0, 6, out total, + ordering: Ordering.By("Path", Direction.Descending)).ToArray(); + Assert.That(entities.Length, Is.EqualTo(6)); + Assert.That(total, Is.EqualTo(10)); + Assert.AreEqual(ids[ids.Count - 1], entities[0].Id); + + entities = service.GetPagedDescendants(rootId, UmbracoObjectTypes.Document, 1, 6, out total, + ordering: Ordering.By("Path", Direction.Descending)).ToArray(); + Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(total, Is.EqualTo(10)); + Assert.AreEqual(ids[ids.Count - 1 - 6], entities[0].Id); + } + [Test] public void EntityService_Can_Get_Paged_Content_Children() { @@ -45,21 +93,41 @@ namespace Umbraco.Tests.Services var root = MockedContent.CreateSimpleContent(contentType); ServiceContext.ContentService.Save(root); + var ids = new List(); for (int i = 0; i < 10; i++) { var c1 = MockedContent.CreateSimpleContent(contentType, Guid.NewGuid().ToString(), root); ServiceContext.ContentService.Save(c1); + ids.Add(c1.Id); } var service = ServiceContext.EntityService; long total; + var entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 0, 6, out total).ToArray(); Assert.That(entities.Length, Is.EqualTo(6)); Assert.That(total, Is.EqualTo(10)); + Assert.AreEqual(ids[0], entities[0].Id); + entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 1, 6, out total).ToArray(); Assert.That(entities.Length, Is.EqualTo(4)); Assert.That(total, Is.EqualTo(10)); + Assert.AreEqual(ids[6], entities[0].Id); + + //Test ordering direction + + entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 0, 6, out total, + ordering: Ordering.By("SortOrder", Direction.Descending)).ToArray(); + Assert.That(entities.Length, Is.EqualTo(6)); + Assert.That(total, Is.EqualTo(10)); + Assert.AreEqual(ids[ids.Count - 1], entities[0].Id); + + entities = service.GetPagedChildren(root.Id, UmbracoObjectTypes.Document, 1, 6, out total, + ordering: Ordering.By("SortOrder", Direction.Descending)).ToArray(); + Assert.That(entities.Length, Is.EqualTo(4)); + Assert.That(total, Is.EqualTo(10)); + Assert.AreEqual(ids[ids.Count - 1 - 6], entities[0].Id); } [Test] @@ -206,10 +274,12 @@ namespace Umbraco.Tests.Services var service = ServiceContext.EntityService; long total; - var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 10, out total, filter: "ssss").ToArray(); + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 10, out total, + filter: SqlContext.Query().Where(x => x.Name.Contains("ssss"))).ToArray(); Assert.That(entities.Length, Is.EqualTo(10)); Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 50, out total, filter: "tttt").ToArray(); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Document, 0, 50, out total, + filter: SqlContext.Query().Where(x => x.Name.Contains("tttt"))).ToArray(); Assert.That(entities.Length, Is.EqualTo(50)); Assert.That(total, Is.EqualTo(50)); } @@ -389,10 +459,12 @@ namespace Umbraco.Tests.Services var service = ServiceContext.EntityService; long total; - var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 10, out total, filter: "ssss").ToArray(); + var entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 10, out total, + filter: SqlContext.Query().Where(x => x.Name.Contains("ssss"))).ToArray(); Assert.That(entities.Length, Is.EqualTo(10)); Assert.That(total, Is.EqualTo(10)); - entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 50, out total, filter: "tttt").ToArray(); + entities = service.GetPagedDescendants(root.Id, UmbracoObjectTypes.Media, 0, 50, out total, + filter: SqlContext.Query().Where(x => x.Name.Contains("tttt"))).ToArray(); Assert.That(entities.Length, Is.EqualTo(50)); Assert.That(total, Is.EqualTo(50)); } diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 8245a21019..2ec92c946e 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1065,7 +1065,7 @@ namespace Umbraco.Web.Editors var descendants = Services.EntityService.GetPagedDescendants(contentItem.Id, UmbracoObjectTypes.Document, page++, pageSize, out total, //order by shallowest to deepest, this allows us to check permissions from top to bottom so we can exit //early if a permission higher up fails - "path", Direction.Ascending); + ordering: Ordering.By("path", Direction.Ascending)); foreach (var c in descendants) { diff --git a/src/Umbraco.Web/Editors/EntityController.cs b/src/Umbraco.Web/Editors/EntityController.cs index 3cecdbe8e5..5e73f4140e 100644 --- a/src/Umbraco.Web/Editors/EntityController.cs +++ b/src/Umbraco.Web/Editors/EntityController.cs @@ -17,6 +17,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; using System.Web.Http.Controllers; using Examine; using Umbraco.Core.Models.Entities; +using Umbraco.Core.Services; using Umbraco.Core.Xml; using Umbraco.Web.Models.Mapping; using Umbraco.Web.Search; @@ -477,7 +478,9 @@ namespace Umbraco.Web.Editors var objectType = ConvertToObjectType(type); if (objectType.HasValue) { - var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out var totalRecords, orderBy, orderDirection, filter); + var entities = Services.EntityService.GetPagedChildren(id, objectType.Value, pageNumber - 1, pageSize, out var totalRecords, + SqlContext.Query().Where(x => x.Name.Contains(filter)), + Ordering.By(orderBy, orderDirection)); if (totalRecords == 0) { @@ -545,12 +548,18 @@ namespace Umbraco.Web.Editors } entities = aids == null || aids.Contains(Constants.System.Root) - ? Services.EntityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter, includeTrashed: false) - : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + ? Services.EntityService.GetPagedDescendants(objectType.Value, pageNumber - 1, pageSize, out totalRecords, + SqlContext.Query().Where(x => x.Name.Contains(filter)), + Ordering.By(orderBy, orderDirection), includeTrashed: false) + : Services.EntityService.GetPagedDescendants(aids, objectType.Value, pageNumber - 1, pageSize, out totalRecords, + SqlContext.Query().Where(x => x.Name.Contains(filter)), + Ordering.By(orderBy, orderDirection)); } else { - entities = Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, orderBy, orderDirection, filter); + entities = Services.EntityService.GetPagedDescendants(id, objectType.Value, pageNumber - 1, pageSize, out totalRecords, + SqlContext.Query().Where(x => x.Name.Contains(filter)), + Ordering.By(orderBy, orderDirection)); } if (totalRecords == 0) diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index 0ef3c073eb..a3d65aea5f 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -6,6 +6,7 @@ using System.Net.Http.Formatting; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Core.Services; using Umbraco.Web.Actions; @@ -144,7 +145,8 @@ namespace Umbraco.Web.Trees public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) { - var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.DocumentType, pageIndex, pageSize, out totalFound, filter: query); + var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.DocumentType, pageIndex, pageSize, out totalFound, + filter: SqlContext.Query().Where(x => x.Name.Contains(query))); return Mapper.Map>(results); } } diff --git a/src/Umbraco.Web/Trees/DataTypeTreeController.cs b/src/Umbraco.Web/Trees/DataTypeTreeController.cs index 0970481357..b8e77f981d 100644 --- a/src/Umbraco.Web/Trees/DataTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/DataTypeTreeController.cs @@ -5,6 +5,7 @@ using System.Net.Http.Formatting; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; using Umbraco.Web.Models.Trees; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi.Filters; @@ -144,7 +145,8 @@ namespace Umbraco.Web.Trees public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) { - var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.DataType, pageIndex, pageSize, out totalFound, filter: query); + var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.DataType, pageIndex, pageSize, out totalFound, + filter: SqlContext.Query().Where(x => x.Name.Contains(query))); return Mapper.Map>(results); } } diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 8b3ad5e8cd..bdcc2dc029 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -6,6 +6,8 @@ using AutoMapper; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Core.Models.Entities; +using Umbraco.Core.Persistence.Querying; using Umbraco.Web.Models.Trees; using Umbraco.Web.WebApi.Filters; using Umbraco.Core.Services; @@ -135,7 +137,8 @@ namespace Umbraco.Web.Trees public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) { - var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.MediaType, pageIndex, pageSize, out totalFound, filter: query); + var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.MediaType, pageIndex, pageSize, out totalFound, + filter: SqlContext.Query().Where(x => x.Name.Contains(query))); return Mapper.Map>(results); } } diff --git a/src/Umbraco.Web/Trees/TemplatesTreeController.cs b/src/Umbraco.Web/Trees/TemplatesTreeController.cs index 56f47695a0..fce51e2435 100644 --- a/src/Umbraco.Web/Trees/TemplatesTreeController.cs +++ b/src/Umbraco.Web/Trees/TemplatesTreeController.cs @@ -135,7 +135,8 @@ namespace Umbraco.Web.Trees public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) { - var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.Template, pageIndex, pageSize, out totalFound, filter: query); + var results = Services.EntityService.GetPagedDescendants(UmbracoObjectTypes.Template, pageIndex, pageSize, out totalFound, + filter: SqlContext.Query().Where(x => x.Name.Contains(query))); return Mapper.Map>(results); } } From 711304dffea5b5fc3c014e89c4b927bc25b457b1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Dec 2018 13:44:18 +1100 Subject: [PATCH 40/45] manual merges --- .../Repositories/Implement/UserRepository.cs | 1 + .../compositions/compositions.controller.js | 40 +++++++++----- .../compositions/compositions.html | 52 +++++++++++-------- .../components/application/umb-login.html | 11 +--- 4 files changed, 60 insertions(+), 44 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index b14c7659a3..08db70ae27 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -423,6 +423,7 @@ ORDER BY colName"; { "DELETE FROM umbracoUser2UserGroup WHERE userId = @id", "DELETE FROM umbracoUser2NodeNotify WHERE userId = @id", + "DELETE FROM umbracoUserStartNode WHERE userId = @Id", "DELETE FROM umbracoUser WHERE id = @id", "DELETE FROM umbracoExternalLogin WHERE id = @id" }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js index 54d5da29a8..75bf414099 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.controller.js @@ -1,7 +1,7 @@ - (function() { - "use strict"; +(function () { + "use strict"; - function CompositionsController($scope,$location) { + function CompositionsController($scope, $location, $filter) { var vm = this; var oldModel = null; @@ -19,18 +19,34 @@ back the changes on cancel */ oldModel = angular.copy($scope.model); - if(!$scope.model.title) { + if (!$scope.model.title) { $scope.model.title = "Compositions"; } + // group the content types by their container paths + vm.availableGroups = $filter("orderBy")( + _.map( + _.groupBy($scope.model.availableCompositeContentTypes, function (compositeContentType) { + return compositeContentType.contentType.metaData.containerPath; + }), function (group) { + return { + containerPath: group[0].contentType.metaData.containerPath, + compositeContentTypes: group + }; + } + ), function (group) { + return group.containerPath.replace(/\//g, " "); + }); } + + function isSelected(alias) { - if($scope.model.contentType.compositeContentTypes.indexOf(alias) !== -1) { + if ($scope.model.contentType.compositeContentTypes.indexOf(alias) !== -1) { return true; } } - + function openContentType(contentType, section) { var url = (section === "documentType" ? "/settings/documenttypes/edit/" : "/settings/mediaTypes/edit/") + contentType.id; $location.path(url); @@ -38,19 +54,19 @@ function submit() { if ($scope.model && $scope.model.submit) { - + // check if any compositions has been removed vm.compositionRemoved = false; - for(var i = 0; oldModel.compositeContentTypes.length > i; i++) { + for (var i = 0; oldModel.compositeContentTypes.length > i; i++) { var oldComposition = oldModel.compositeContentTypes[i]; - if(_.contains($scope.model.compositeContentTypes, oldComposition) === false) { + if (_.contains($scope.model.compositeContentTypes, oldComposition) === false) { vm.compositionRemoved = true; } } /* submit the form if there havne't been removed any composition or the confirm checkbox has been checked */ - if(!vm.compositionRemoved || vm.allowSubmit) { + if (!vm.compositionRemoved || vm.allowSubmit) { $scope.model.submit($scope.model); } } @@ -63,8 +79,8 @@ } onInit(); - } + } - angular.module("umbraco").controller("Umbraco.Editors.CompositionsController", CompositionsController); + angular.module("umbraco").controller("Umbraco.Editors.CompositionsController", CompositionsController); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html index 5d7a5420db..84fbab7cb2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html @@ -60,29 +60,35 @@
-
    -
  • - -
    - -
    - - - -
  • -
+
+
    +
  • + + {{group.containerPath}} +
  • +
  • + +
    + +
    + + + +
  • +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html index 7d8ce3e13e..2ce49880d7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html @@ -97,14 +97,7 @@ - -
@@ -268,4 +261,4 @@
- \ No newline at end of file + From d1befc876fb64761e1c4f770b7a667964ea565c2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 21 Dec 2018 13:48:59 +1100 Subject: [PATCH 41/45] manual merges --- src/Umbraco.Web/Trees/MediaTreeController.cs | 25 +++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web/Trees/MediaTreeController.cs b/src/Umbraco.Web/Trees/MediaTreeController.cs index 5fa3714d3f..d88b2b9402 100644 --- a/src/Umbraco.Web/Trees/MediaTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTreeController.cs @@ -121,24 +121,27 @@ namespace Umbraco.Web.Trees return menu; } - //return a normal node menu: - menu.Items.Add(Services.TextService, opensDialog: true); - menu.Items.Add(Services.TextService, opensDialog: true); - menu.Items.Add(Services.TextService, opensDialog: true); - menu.Items.Add(Services.TextService); - menu.Items.Add(new RefreshNode(Services.TextService, true)); - //if the media item is in the recycle bin, don't have a default menu, just show the regular menu + //if the media item is in the recycle bin, we don't have a default menu and we need to show a limited menu if (item.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries).Contains(RecycleBinId.ToInvariantString())) { + menu.Items.Add(Services.TextService, opensDialog: true); + menu.Items.Add(Services.TextService, opensDialog: true); + menu.Items.Add(Services.TextService, opensDialog: true); + menu.Items.Add(new RefreshNode(Services.TextService, true)); + menu.DefaultMenuAlias = null; - menu.Items.Insert(2, new MenuItem(ActionRestore.ActionAlias, Services.TextService) - { - OpensDialog = true - }); + } else { + //return a normal node menu: + menu.Items.Add(Services.TextService, opensDialog: true); + menu.Items.Add(Services.TextService, opensDialog: true); + menu.Items.Add(Services.TextService, opensDialog: true); + menu.Items.Add(Services.TextService); + menu.Items.Add(new RefreshNode(Services.TextService, true)); + //set the default to create menu.DefaultMenuAlias = ActionNew.ActionAlias; } From 9db0f540d0d5fd3f51c8acb0a317455d7b4a2930 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 21 Dec 2018 08:58:20 +0100 Subject: [PATCH 42/45] Fix failing tests --- .../Persistence/Repositories/Implement/UserRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 08db70ae27..17fe79a55b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -423,7 +423,7 @@ ORDER BY colName"; { "DELETE FROM umbracoUser2UserGroup WHERE userId = @id", "DELETE FROM umbracoUser2NodeNotify WHERE userId = @id", - "DELETE FROM umbracoUserStartNode WHERE userId = @Id", + "DELETE FROM umbracoUserStartNode WHERE userId = @id", "DELETE FROM umbracoUser WHERE id = @id", "DELETE FROM umbracoExternalLogin WHERE id = @id" }; From 90ab90d6b9b6bd5b63db0d15bc62d13c4946bdf7 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 21 Dec 2018 13:42:04 +0100 Subject: [PATCH 43/45] Fix ExamineComponent crashing on shutdown --- src/Umbraco.Web/Search/ExamineComponent.cs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/Search/ExamineComponent.cs b/src/Umbraco.Web/Search/ExamineComponent.cs index 71061a5cb3..fff8e99aeb 100644 --- a/src/Umbraco.Web/Search/ExamineComponent.cs +++ b/src/Umbraco.Web/Search/ExamineComponent.cs @@ -93,6 +93,11 @@ namespace Umbraco.Web.Search false)); composition.Container.RegisterSingleton, MediaValueSetBuilder>(); composition.Container.RegisterSingleton, MemberValueSetBuilder>(); + + //We want to manage Examine's appdomain shutdown sequence ourselves so first we'll disable Examine's default behavior + //and then we'll use MainDom to control Examine's shutdown - this MUST be done in Compose ie before ExamineManager + //is instantiated, as the value is used during instantiation + ExamineManager.DisableDefaultHostingEnvironmentRegistration(); } internal void Initialize(IRuntimeState runtime, MainDom mainDom, PropertyEditorCollection propertyEditors, @@ -112,10 +117,6 @@ namespace Umbraco.Web.Search _mediaValueSetBuilder = mediaValueSetBuilder; _memberValueSetBuilder = memberValueSetBuilder; - //We want to manage Examine's appdomain shutdown sequence ourselves so first we'll disable Examine's default behavior - //and then we'll use MainDom to control Examine's shutdown - ExamineManager.DisableDefaultHostingEnvironmentRegistration(); - //we want to tell examine to use a different fs lock instead of the default NativeFSFileLock which could cause problems if the appdomain //terminates and in some rare cases would only allow unlocking of the file if IIS is forcefully terminated. Instead we'll rely on the simplefslock //which simply checks the existence of the lock file @@ -203,7 +204,7 @@ namespace Umbraco.Web.Search } } - + /// /// Must be called to each index is unlocked before any indexing occurs @@ -581,7 +582,7 @@ namespace Umbraco.Web.Search } } - + #endregion #region ReIndex/Delete for entity @@ -782,7 +783,7 @@ namespace Umbraco.Web.Search { var strId = id.ToString(CultureInfo.InvariantCulture); foreach (var index in examineComponent._examineManager.Indexes.OfType() - + .Where(x => (keepIfUnpublished && !x.PublishedValuesOnly) || !keepIfUnpublished) .Where(x => x.EnableDefaultEventHandler)) { From a571af8be2438a03cff4bb585079b31437f77af1 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 21 Dec 2018 13:42:24 +0100 Subject: [PATCH 44/45] Make more types IDiscoverable --- src/Umbraco.Core/Migrations/IPostMigration.cs | 3 ++- .../NotificationMethods/IHealthCheckNotificationMethod.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Migrations/IPostMigration.cs b/src/Umbraco.Core/Migrations/IPostMigration.cs index 827a09e882..15daf0fc74 100644 --- a/src/Umbraco.Core/Migrations/IPostMigration.cs +++ b/src/Umbraco.Core/Migrations/IPostMigration.cs @@ -1,10 +1,11 @@ using Semver; +using Umbraco.Core.Composing; using Umbraco.Core.Logging; using Umbraco.Core.Scoping; namespace Umbraco.Core.Migrations { - public interface IPostMigration + public interface IPostMigration : IDiscoverable { void Execute(string name, IScope scope, SemVersion originVersion, SemVersion targetVersion, ILogger logger); } diff --git a/src/Umbraco.Web/HealthCheck/NotificationMethods/IHealthCheckNotificationMethod.cs b/src/Umbraco.Web/HealthCheck/NotificationMethods/IHealthCheckNotificationMethod.cs index 2b8009d201..f6e8f1d1c5 100644 --- a/src/Umbraco.Web/HealthCheck/NotificationMethods/IHealthCheckNotificationMethod.cs +++ b/src/Umbraco.Web/HealthCheck/NotificationMethods/IHealthCheckNotificationMethod.cs @@ -1,9 +1,10 @@ using System.Threading; using System.Threading.Tasks; +using Umbraco.Core.Composing; namespace Umbraco.Web.HealthCheck.NotificationMethods { - public interface IHealthCheckNotificationMethod + public interface IHealthCheckNotificationMethod : IDiscoverable { bool Enabled { get; } Task SendAsync(HealthCheckResults results, CancellationToken token); From 29540b18350451d2c994354749b78bcf6716eba7 Mon Sep 17 00:00:00 2001 From: Stephan Date: Fri, 21 Dec 2018 15:11:23 +0100 Subject: [PATCH 45/45] TypeLoader TLC, better perfs and logging --- src/Umbraco.Core/Composing/TypeLoader.cs | 172 +++++++++++++---------- 1 file changed, 97 insertions(+), 75 deletions(-) diff --git a/src/Umbraco.Core/Composing/TypeLoader.cs b/src/Umbraco.Core/Composing/TypeLoader.cs index 78b9943976..ef58671e91 100644 --- a/src/Umbraco.Core/Composing/TypeLoader.cs +++ b/src/Umbraco.Core/Composing/TypeLoader.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Web; using System.Web.Compilation; using Umbraco.Core.Cache; +using Umbraco.Core.Collections; using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.Logging; @@ -16,7 +17,7 @@ using File = System.IO.File; namespace Umbraco.Core.Composing { /// - /// Provides methods to find and instanciate types. + /// Provides methods to find and instantiate types. /// /// /// This class should be used to get all types, the class should never be used directly. @@ -32,9 +33,12 @@ namespace Umbraco.Core.Composing private readonly IGlobalSettings _globalSettings; private readonly ProfilingLogger _logger; + private readonly Dictionary _types = new Dictionary(); private readonly object _typesLock = new object(); - private readonly Dictionary _types = new Dictionary(); + private readonly object _timerLock = new object(); + private Timer _timer; + private bool _timing; private string _cachedAssembliesHash; private string _currentAssembliesHash; private IEnumerable _assemblies; @@ -78,8 +82,7 @@ namespace Umbraco.Core.Composing // rescanning of all types including lazy ones. // http://issues.umbraco.org/issue/U4-4789 var typesListFilePath = GetTypesListFilePath(); - if (File.Exists(typesListFilePath)) - File.Delete(typesListFilePath); + DeleteFile(typesListFilePath, FileDeleteTimeout); WriteCacheTypesHash(); } @@ -90,8 +93,7 @@ namespace Umbraco.Core.Composing // rescanning of all types including lazy ones. // http://issues.umbraco.org/issue/U4-4789 var typesListFilePath = GetTypesListFilePath(); - if (File.Exists(typesListFilePath)) - File.Delete(typesListFilePath); + DeleteFile(typesListFilePath, FileDeleteTimeout); // always set to true if we're not detecting (generally only for testing) RequiresRescanning = true; @@ -128,7 +130,8 @@ namespace Umbraco.Core.Composing // internal for tests internal void AddTypeList(TypeList typeList) { - _types[new TypeListKey(typeList.BaseType, typeList.AttributeType)] = typeList; + var tobject = typeof(object); // CompositeTypeTypeKey does not support null values + _types[new CompositeTypeTypeKey(typeList.BaseType ?? tobject, typeList.AttributeType ?? tobject)] = typeList; } #region Hashing @@ -287,11 +290,14 @@ namespace Umbraco.Core.Composing private const int ListFileOpenReadTimeout = 4000; // milliseconds private const int ListFileOpenWriteTimeout = 2000; // milliseconds + private const int ListFileWriteThrottle = 500; // milliseconds - throttle before writing + private const int ListFileCacheDuration = 2 * 60; // seconds - duration we cache the entire list + private const int FileDeleteTimeout = 4000; // milliseconds // internal for tests internal Attempt> TryGetCached(Type baseType, Type attributeType) { - var cache = _runtimeCache.GetCacheItem, IEnumerable>>(CacheKey, ReadCacheSafe, TimeSpan.FromMinutes(4)); + var cache = _runtimeCache.GetCacheItem, IEnumerable>>(CacheKey, ReadCacheSafe, TimeSpan.FromSeconds(ListFileCacheDuration)); cache.TryGetValue(Tuple.Create(baseType == null ? string.Empty : baseType.FullName, attributeType == null ? string.Empty : attributeType.FullName), out IEnumerable types); return types == null @@ -310,7 +316,7 @@ namespace Umbraco.Core.Composing try { var typesListFilePath = GetTypesListFilePath(); - File.Delete(typesListFilePath); + DeleteFile(typesListFilePath, FileDeleteTimeout); } catch { @@ -415,6 +421,7 @@ namespace Umbraco.Core.Composing // internal for tests internal void WriteCache() { + _logger.Logger.Debug("Writing cache file."); var typesListFilePath = GetTypesListFilePath(); using (var stream = GetFileStream(typesListFilePath, FileMode.Create, FileAccess.Write, FileShare.None, ListFileOpenWriteTimeout)) using (var writer = new StreamWriter(stream)) @@ -433,11 +440,27 @@ namespace Umbraco.Core.Composing // internal for tests internal void UpdateCache() { - // note - // at the moment we write the cache to disk every time we update it. ideally we defer the writing - // since all the updates are going to happen in a row when Umbraco starts. that being said, the - // file is small enough, so it is not a priority. - WriteCache(); + void TimerRelease(object o) + { + lock (_timerLock) + { + try + { + WriteCache(); + } + catch { /* bah - just don't die */ } + if (!_timing) _timer = null; + } + } + + lock (_timerLock) + { + if (_timer == null) + _timer = new Timer(TimerRelease, null, ListFileWriteThrottle, Timeout.Infinite); + else + _timer.Change(ListFileWriteThrottle, Timeout.Infinite); + _timing = true; + } } /// @@ -447,12 +470,10 @@ namespace Umbraco.Core.Composing public void ClearTypesCache() { var typesListFilePath = GetTypesListFilePath(); - if (File.Exists(typesListFilePath)) - File.Delete(typesListFilePath); + DeleteFile(typesListFilePath, FileDeleteTimeout); var typesHashFilePath = GetTypesHashFilePath(); - if (File.Exists(typesHashFilePath)) - File.Delete(typesHashFilePath); + DeleteFile(typesHashFilePath, FileDeleteTimeout); _runtimeCache.ClearCacheItem(CacheKey); } @@ -478,6 +499,27 @@ namespace Umbraco.Core.Composing } } + private void DeleteFile(string path, int timeoutMilliseconds) + { + const int pauseMilliseconds = 250; + var attempts = timeoutMilliseconds / pauseMilliseconds; + while (File.Exists(path)) + { + try + { + File.Delete(path); + } + catch + { + if (--attempts == 0) + throw; + + _logger.Logger.Debug("Attempted to delete file {Path} failed, {NumberOfAttempts} attempts left, pausing for {PauseMilliseconds} milliseconds", path, attempts, pauseMilliseconds); + Thread.Sleep(pauseMilliseconds); + } + } + } + #endregion #region Get Types @@ -495,30 +537,37 @@ namespace Umbraco.Core.Composing // do not cache anything from specific assemblies cache &= specificAssemblies == null; - // if not caching, or not IDiscoverable, directly get types - if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false) + // if not IDiscoverable, directly get types + if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T))) { - _logger.Logger.Debug("Running a full, non-cached, scan for type {TypeName} (slow).", typeof(T).FullName); + // warn + _logger.Logger.Debug("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} (slow).", typeof(T).FullName); return GetTypesInternal( - typeof (T), null, + typeof(T), null, () => TypeFinder.FindClassesOfType(specificAssemblies ?? AssembliesToScan), + "scanning assemblies", cache); } - // if caching and IDiscoverable - // filter the cached discovered types (and cache the result) - + // get IDiscoverable and always cache var discovered = GetTypesInternal( typeof (IDiscoverable), null, () => TypeFinder.FindClassesOfType(AssembliesToScan), + "scanning assemblies", true); + // warn + if (!cache) + _logger.Logger.Debug("Running a non-cached, filter for discoverable type {TypeName} (slowish).", typeof(T).FullName); + + // filter the cached discovered types (and maybe cache the result) return GetTypesInternal( typeof (T), null, () => discovered .Where(x => typeof (T).IsAssignableFrom(x)), - true); + "filtering IDiscoverable", + cache); } /// @@ -536,31 +585,37 @@ namespace Umbraco.Core.Composing // do not cache anything from specific assemblies cache &= specificAssemblies == null; - // if not caching, or not IDiscoverable, directly get types - if (cache == false || typeof(IDiscoverable).IsAssignableFrom(typeof(T)) == false) + // if not IDiscoverable, directly get types + if (!typeof(IDiscoverable).IsAssignableFrom(typeof(T))) { - _logger.Logger.Debug("Running a full, non-cached, scan for type {TypeName} / attribute {AttributeName} (slow).", typeof(T).FullName, typeof(TAttribute).FullName); + _logger.Logger.Debug("Running a full, " + (cache ? "" : "non-") + "cached, scan for non-discoverable type {TypeName} / attribute {AttributeName} (slow).", typeof(T).FullName, typeof(TAttribute).FullName); return GetTypesInternal( - typeof (T), typeof (TAttribute), + typeof(T), typeof(TAttribute), () => TypeFinder.FindClassesOfTypeWithAttribute(specificAssemblies ?? AssembliesToScan), + "scanning assemblies", cache); } - // if caching and IDiscoverable - // filter the cached discovered types (and cache the result) - + // get IDiscoverable and always cache var discovered = GetTypesInternal( typeof (IDiscoverable), null, () => TypeFinder.FindClassesOfType(AssembliesToScan), + "scanning assemblies", true); + // warn + if (!cache) + _logger.Logger.Debug("Running a non-cached, filter for discoverable type {TypeName} / attribute {AttributeName} (slowish).", typeof(T).FullName, typeof(TAttribute).FullName); + + // filter the cached discovered types (and maybe cache the result) return GetTypesInternal( typeof (T), typeof (TAttribute), () => discovered .Where(x => typeof(T).IsAssignableFrom(x)) .Where(x => x.GetCustomAttributes(false).Any()), - true); + "filtering IDiscoverable", + cache); } /// @@ -577,20 +632,20 @@ namespace Umbraco.Core.Composing // do not cache anything from specific assemblies cache &= specificAssemblies == null; - if (cache == false) - { + if (!cache) _logger.Logger.Debug("Running a full, non-cached, scan for types / attribute {AttributeName} (slow).", typeof(TAttribute).FullName); - } return GetTypesInternal( typeof (object), typeof (TAttribute), () => TypeFinder.FindClassesWithAttribute(specificAssemblies ?? AssembliesToScan), + "scanning assemblies", cache); } private IEnumerable GetTypesInternal( Type baseType, Type attributeType, Func> finder, + string action, bool cache) { // using an upgradeable lock makes little sense here as only one thread can enter the upgradeable @@ -606,7 +661,7 @@ namespace Umbraco.Core.Composing "Got " + name)) // cannot contain typesFound.Count as it's evaluated before the find { // get within a lock & timer - return GetTypesInternalLocked(baseType, attributeType, finder, cache); + return GetTypesInternalLocked(baseType, attributeType, finder, action, cache); } } @@ -620,10 +675,12 @@ namespace Umbraco.Core.Composing private IEnumerable GetTypesInternalLocked( Type baseType, Type attributeType, Func> finder, + string action, bool cache) { // check if the TypeList already exists, if so return it, if not we'll create it - var listKey = new TypeListKey(baseType, attributeType); + var tobject = typeof(object); // CompositeTypeTypeKey does not support null values + var listKey = new CompositeTypeTypeKey(baseType ?? tobject, attributeType ?? tobject); TypeList typeList = null; if (cache) _types.TryGetValue(listKey, out typeList); // else null @@ -698,7 +755,7 @@ namespace Umbraco.Core.Composing if (scan) { // either we had to scan, or we could not get the types from the cache file - scan now - _logger.Logger.Debug("Getting {TypeName}: scanning assemblies.", GetName(baseType, attributeType)); + _logger.Logger.Debug("Getting {TypeName}: " + action + ".", GetName(baseType, attributeType)); foreach (var t in finder()) typeList.Add(t); @@ -730,41 +787,6 @@ namespace Umbraco.Core.Composing #region Nested classes and stuff - /// - /// Groups a type and a resolution kind into a key. - /// - private struct TypeListKey - { - // ReSharper disable MemberCanBePrivate.Local - public readonly Type BaseType; - public readonly Type AttributeType; - // ReSharper restore MemberCanBePrivate.Local - - public TypeListKey(Type baseType, Type attributeType) - { - BaseType = baseType ?? typeof (object); - AttributeType = attributeType; - } - - public override bool Equals(object obj) - { - if (obj == null || obj is TypeListKey == false) return false; - var o = (TypeListKey)obj; - return BaseType == o.BaseType && AttributeType == o.AttributeType; - } - - public override int GetHashCode() - { - // in case AttributeType is null we need something else, using typeof (TypeListKey) - // which does not really "mean" anything, it's just a value... - - var hash = 5381; - hash = ((hash << 5) + hash) ^ BaseType.GetHashCode(); - hash = ((hash << 5) + hash) ^ (AttributeType ?? typeof (TypeListKey)).GetHashCode(); - return hash; - } - } - /// /// Represents a list of types obtained by looking for types inheriting/implementing a /// specified type, and/or marked with a specified attribute type.