From 8a60c95a36242ee6cbae36232599bed38e902ec9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 12 Sep 2019 17:04:29 +1000 Subject: [PATCH 001/393] Fixes default weighting for Umbraco.Web handlers and adds unit test --- .../ApplicationEventsResolver.cs | 14 +++++--- .../ApplicationEventsResolverTests.cs | 32 +++++++++++++++++++ .../Resolvers/LazyManyObjectResolverTests.cs | 6 ++-- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + 4 files changed, 46 insertions(+), 7 deletions(-) create mode 100644 src/Umbraco.Tests/Resolvers/ApplicationEventsResolverTests.cs diff --git a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs index eda1b94e37..20c49de588 100644 --- a/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs +++ b/src/Umbraco.Core/ObjectResolution/ApplicationEventsResolver.cs @@ -89,16 +89,20 @@ namespace Umbraco.Core.ObjectResolution } } - protected override int GetObjectWeight(object o) - { + protected override int GetObjectWeight(object o) => GetObjectWeightInternal(o, DefaultPluginWeight); + + internal static int GetObjectWeightInternal(object o, int defaultPluginWeight) + { var type = o.GetType(); var attr = type.GetCustomAttribute(true); if (attr != null) return attr.Weight; var name = type.Assembly.FullName; // we should really attribute all our Core handlers, so this is temp - var core = name.InvariantStartsWith("Umbraco.") || name.InvariantStartsWith("Concorde."); - return core ? -DefaultPluginWeight : DefaultPluginWeight; + var core = name.InvariantStartsWith("umbraco,") // This handles the umbraco.dll (Umbraco.Web) project + || name.InvariantStartsWith("Umbraco.") // This handles all other Umbraco.* assemblies - in the case of v7, this is ONLY Umbraco.Core + || name.InvariantStartsWith("Concorde."); // Special case for Cloud assemblies + return core ? -defaultPluginWeight : defaultPluginWeight; } /// @@ -202,4 +206,4 @@ namespace Umbraco.Core.ObjectResolution _orderedAndFiltered = null; } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/Resolvers/ApplicationEventsResolverTests.cs b/src/Umbraco.Tests/Resolvers/ApplicationEventsResolverTests.cs new file mode 100644 index 0000000000..3fba16fde3 --- /dev/null +++ b/src/Umbraco.Tests/Resolvers/ApplicationEventsResolverTests.cs @@ -0,0 +1,32 @@ +using NUnit.Framework; +using umbraco.BusinessLogic; +using Umbraco.Core; +using Umbraco.Core.Models.Identity; +using Umbraco.Core.ObjectResolution; +using Umbraco.Web.PropertyEditors; + +namespace Umbraco.Tests.Resolvers +{ + [TestFixture] + public class ApplicationEventsResolverTests + { + [Test] + public void Core_Event_Handler_Weight_Test() + { + //from the 'umbraco' (Umbraco.Web) assembly + Assert.AreEqual(-100, ApplicationEventsResolver.GetObjectWeightInternal(new GridPropertyEditor(), 100)); + //from the 'Umbraco.Core' assembly + Assert.AreEqual(-100, ApplicationEventsResolver.GetObjectWeightInternal(new IdentityModelMappings(), 100)); + //from the 'Umbraco.Test' assembly + Assert.AreEqual(-100, ApplicationEventsResolver.GetObjectWeightInternal(new MyTestEventHandler(), 100)); + + //from the 'umbraco.BusinessLogic' assembly - which we are not checking for and not setting as the negative of the default + Assert.AreEqual(100, ApplicationEventsResolver.GetObjectWeightInternal(new ApplicationRegistrar(), 100)); + } + + private class MyTestEventHandler : ApplicationEventHandler + { + + } + } +} diff --git a/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs b/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs index 33bb1ab34c..841c302e52 100644 --- a/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs +++ b/src/Umbraco.Tests/Resolvers/LazyManyObjectResolverTests.cs @@ -10,7 +10,9 @@ using Umbraco.Core.ObjectResolution; namespace Umbraco.Tests.Resolvers { - [TestFixture] + + + [TestFixture] public class LazyManyObjectResolverTests { @@ -191,4 +193,4 @@ namespace Umbraco.Tests.Resolvers #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index d45c556d37..4a67223b3a 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -197,6 +197,7 @@ + From 905b2414263975c0eeb79066281a0479b5988fd6 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Thu, 26 Sep 2019 13:39:53 +0200 Subject: [PATCH 002/393] #2996 added GlobalSettings.DebugMode switch for JSON formatting indentation. (cherry picked from commits d3c4aace160b78739a8eeb673cb561e96cc04101 / 16837d018a44c324e620d7c72b63be015a87895c) --- src/Umbraco.Core/Serialization/JsonNetSerializer.cs | 6 ++++-- src/Umbraco.Web/Editors/BackOfficeController.cs | 12 ++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Core/Serialization/JsonNetSerializer.cs b/src/Umbraco.Core/Serialization/JsonNetSerializer.cs index 800278abf0..52f39c6109 100644 --- a/src/Umbraco.Core/Serialization/JsonNetSerializer.cs +++ b/src/Umbraco.Core/Serialization/JsonNetSerializer.cs @@ -4,6 +4,7 @@ using System.Reflection; using System.Text; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using Umbraco.Core.Configuration; namespace Umbraco.Core.Serialization { @@ -60,7 +61,8 @@ namespace Umbraco.Core.Serialization /// public IStreamedResult ToStream(object input) { - string s = JsonConvert.SerializeObject(input, Formatting.Indented, _settings); + var formatting = GlobalSettings.DebugMode ? Formatting.Indented : Formatting.None; + string s = JsonConvert.SerializeObject(input, formatting, _settings); byte[] bytes = Encoding.UTF8.GetBytes(s); MemoryStream ms = new MemoryStream(bytes); @@ -69,4 +71,4 @@ namespace Umbraco.Core.Serialization #endregion } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 093c095b5d..4a79038fc0 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -177,8 +177,8 @@ namespace Umbraco.Web.Editors //the dictionary returned is fine but the delimiter between an 'area' and a 'value' is a '/' but the javascript // in the back office requres the delimiter to be a '_' so we'll just replace it .ToDictionary(key => key.Key.Replace("/", "_"), val => val.Value); - - return new JsonNetResult { Data = textForCulture, Formatting = Formatting.Indented }; + var formatting = GlobalSettings.DebugMode ? Formatting.Indented : Formatting.None; + return new JsonNetResult { Data = textForCulture, Formatting = formatting }; } /// @@ -230,8 +230,8 @@ namespace Umbraco.Web.Editors typeof(BackOfficeController) + "GetManifestAssetList", () => getResult(), new TimeSpan(0, 10, 0)); - - return new JsonNetResult { Data = result, Formatting = Formatting.Indented }; + var formatting = GlobalSettings.DebugMode ? Formatting.Indented : Formatting.None; + return new JsonNetResult { Data = result, Formatting = formatting }; } [UmbracoAuthorize(Order = 0)] @@ -244,8 +244,8 @@ namespace Umbraco.Web.Editors new DirectoryInfo(Server.MapPath(SystemDirectories.AppPlugins)), new DirectoryInfo(Server.MapPath(SystemDirectories.Config)), HttpContext.IsDebuggingEnabled); - - return new JsonNetResult { Data = gridConfig.EditorsConfig.Editors, Formatting = Formatting.Indented }; + var formatting = GlobalSettings.DebugMode ? Formatting.Indented : Formatting.None; + return new JsonNetResult { Data = gridConfig.EditorsConfig.Editors, Formatting = formatting }; } From a37b1075a100b3b50b32382bb6af68477f6179a5 Mon Sep 17 00:00:00 2001 From: Benjamin Howarth <322383+benjaminhowarth1@users.noreply.github.com> Date: Mon, 30 Sep 2019 16:54:28 +0100 Subject: [PATCH 003/393] #2996 resubmitting ContentExtensions and ObjectExtensions fixes (#6473) (cherry picked from commit 79bf9b753caf97a55d474c5c6db8821a33ca398f) --- src/Umbraco.Core/ObjectExtensions.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 479e425c99..5c1c5fdeac 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -8,7 +8,10 @@ using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Xml; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Core.Collections; +using Formatting = Newtonsoft.Json.Formatting; namespace Umbraco.Core { @@ -125,6 +128,11 @@ namespace Umbraco.Core return Attempt.Succeed(input); } + if (target == typeof(string) && inputType == typeof(JObject)) + { + return Attempt.Succeed(JsonConvert.SerializeObject(input, Formatting.None)); + } + // Check for string so that overloaders of ToString() can take advantage of the conversion. if (target == typeof(string)) { From f967f2111938d8954b5a61b7db54f23c43c45b49 Mon Sep 17 00:00:00 2001 From: Dirk De Grave Date: Tue, 1 Oct 2019 13:39:32 +0200 Subject: [PATCH 004/393] Fixes #6499 by reloading the content in list view after bulk copy/move --- .../listview/listview.controller.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 9b4c01f4f9..0812bcff3e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -472,7 +472,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie // a specific value from one of the methods, so we'll have to try this way. Even though the first method // will fire once per every node moved, the destination path will be the same and we need to use that to sync. var newPath = null; - applySelected( + var attempt = applySelected( function(selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }) .then(function(path) { @@ -509,6 +509,11 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie }); } }); + if (attempt) { + attempt.then(function () { + $scope.getContent(); + }); + } } $scope.copy = function () { @@ -536,7 +541,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie }; function performCopy(target, relateToOriginal) { - applySelected( + var attempt = applySelected( function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, function (count, total) { var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems"); @@ -546,6 +551,11 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie var key = (total === 1 ? "bulk_copiedItem" : "bulk_copiedItems"); return localizationService.localize(key, [total]); }); + if (attempt) { + attempt.then(function () { + $scope.getContent(); + }); + } } function getCustomPropertyValue(alias, properties) { From b05ef082fdb76e923b50461cad42d668b4ed5448 Mon Sep 17 00:00:00 2001 From: Dirk De Grave Date: Tue, 1 Oct 2019 13:39:32 +0200 Subject: [PATCH 005/393] Fixes #6499 by reloading the content in list view after bulk copy/move (cherry picked from commit f967f2111938d8954b5a61b7db54f23c43c45b49) --- .../listview/listview.controller.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js index 9b4c01f4f9..0812bcff3e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js @@ -472,7 +472,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie // a specific value from one of the methods, so we'll have to try this way. Even though the first method // will fire once per every node moved, the destination path will be the same and we need to use that to sync. var newPath = null; - applySelected( + var attempt = applySelected( function(selected, index) { return contentResource.move({ parentId: target.id, id: getIdCallback(selected[index]) }) .then(function(path) { @@ -509,6 +509,11 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie }); } }); + if (attempt) { + attempt.then(function () { + $scope.getContent(); + }); + } } $scope.copy = function () { @@ -536,7 +541,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie }; function performCopy(target, relateToOriginal) { - applySelected( + var attempt = applySelected( function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); }, function (count, total) { var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems"); @@ -546,6 +551,11 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie var key = (total === 1 ? "bulk_copiedItem" : "bulk_copiedItems"); return localizationService.localize(key, [total]); }); + if (attempt) { + attempt.then(function () { + $scope.getContent(); + }); + } } function getCustomPropertyValue(alias, properties) { From 9c28a1ff808d4ca0059002cd78d90eab7676bf69 Mon Sep 17 00:00:00 2001 From: jmayntzhusen Date: Tue, 1 Oct 2019 20:40:46 +0200 Subject: [PATCH 006/393] Update styles of API docs to match new identity --- apidocs/umbracotemplate/styles/main.css | 274 ++++++++++++++++++------ 1 file changed, 208 insertions(+), 66 deletions(-) diff --git a/apidocs/umbracotemplate/styles/main.css b/apidocs/umbracotemplate/styles/main.css index d74d51b150..69dde09875 100644 --- a/apidocs/umbracotemplate/styles/main.css +++ b/apidocs/umbracotemplate/styles/main.css @@ -1,73 +1,215 @@ body { - color: rgba(0,0,0,.8); -} -.navbar-inverse { - background: #a3db78; -} -.navbar-inverse .navbar-nav>li>a, .navbar-inverse .navbar-text { - color: rgba(0,0,0,.8); -} - -.navbar-inverse { - border-color: transparent; -} - -.sidetoc { - background-color: #f5fbf1; -} -body .toc { - background-color: #f5fbf1; -} -.sidefilter { - background-color: #daf0c9; -} -.subnav { - background-color: #f5fbf1; -} - -.navbar-inverse .navbar-nav>.active>a { - color: rgba(0,0,0,.8); - background-color: #daf0c9; -} - -.navbar-inverse .navbar-nav>.active>a:focus, .navbar-inverse .navbar-nav>.active>a:hover { - color: rgba(0,0,0,.8); - background-color: #daf0c9; -} - -.btn-primary { - color: rgba(0,0,0,.8); - background-color: #fff; - border-color: rgba(0,0,0,.8); -} -.btn-primary:hover { - background-color: #daf0c9; - color: rgba(0,0,0,.8); - border-color: rgba(0,0,0,.8); -} - -.toc .nav > li > a { - color: rgba(0,0,0,.8); -} - -button, a { - color: #f36f21; -} - -button:hover, -button:focus, -a:hover, -a:focus { - color: #143653; - text-decoration: none; + color: #34393e; + font-family: 'Roboto', sans-serif; + line-height: 1.5; + font-size: 16px; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; + word-wrap: break-word } .navbar-header .navbar-brand { - background: url(https://our.umbraco.com/assets/images/logo.svg) left center no-repeat; - background-size: 40px auto; - width:50px; + background: url(https://our.umbraco.com/assets/images/logo.svg) left center no-repeat; + background-size: 40px auto; + width:50px; } -.toc .nav > li.active > a { - color: #f36f21; +#_content>a { + margin-top: 5px; } +/* HEADINGS */ + +h1 { + font-weight: 600; + font-size: 32px; +} + +h2 { + font-weight: 600; + font-size: 24px; + line-height: 1.8; +} + +h3 { + font-weight: 600; + font-size: 20px; + line-height: 1.8; +} + +h5 { + font-size: 14px; + padding: 10px 0px; +} + +article h1, +article h2, +article h3, +article h4 { + margin-top: 35px; + margin-bottom: 15px; +} + +article h4 { + padding-bottom: 8px; + border-bottom: 2px solid #ddd; +} + +/* NAVBAR */ + +.navbar-brand>img { + color: #fff; +} + +.navbar { + border: none; + /* Both navbars use box-shadow */ + -webkit-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); + -moz-box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); + box-shadow: 0px 1px 3px 0px rgba(100, 100, 100, 0.5); +} + +.subnav { + border-top: 1px solid #ddd; + background-color: #fff; +} + +.navbar-inverse { + background-color: #303ea1; + z-index: 100; +} + +.navbar-inverse .navbar-nav>li>a, +.navbar-inverse .navbar-text { + color: #fff; + background-color: #303ea1; + border-bottom: 3px solid transparent; + padding-bottom: 12px; +} + +.navbar-inverse .navbar-nav>li>a:focus, +.navbar-inverse .navbar-nav>li>a:hover { + color: #fff; + background-color: #303ea1; + border-bottom: 3px solid white; +} + +.navbar-inverse .navbar-nav>.active>a, +.navbar-inverse .navbar-nav>.active>a:focus, +.navbar-inverse .navbar-nav>.active>a:hover { + color: #fff; + background-color: #303ea1; + border-bottom: 3px solid white; +} + +.navbar-form .form-control { + border: none; + border-radius: 20px; +} + +/* SIDEBAR */ + +.toc .level1>li { + font-weight: 400; +} + +.toc .nav>li>a { + color: #34393e; +} + +.sidefilter { + background-color: #fff; + border-left: none; + border-right: none; +} + +.sidefilter { + background-color: #fff; + border-left: none; + border-right: none; +} + +.toc-filter { + padding: 10px; + margin: 0; +} + +.toc-filter>input { + border: 2px solid #ddd; + border-radius: 20px; +} + +.toc-filter>.filter-icon { + display: none; +} + +.sidetoc>.toc { + background-color: #fff; + overflow-x: hidden; +} + +.sidetoc { + background-color: #fff; + border: none; +} + +/* ALERTS */ + +.alert { + padding: 0px 0px 5px 0px; + color: inherit; + background-color: inherit; + border: none; + box-shadow: 0px 2px 2px 0px rgba(100, 100, 100, 0.4); +} + +.alert>p { + margin-bottom: 0; + padding: 5px 10px; +} + +.alert>ul { + margin-bottom: 0; + padding: 5px 40px; +} + +.alert>h5 { + padding: 10px 15px; + margin-top: 0; + text-transform: uppercase; + font-weight: bold; + border-radius: 4px 4px 0 0; +} + +.alert-info>h5 { + color: #1976d2; + border-bottom: 4px solid #1976d2; + background-color: #e3f2fd; +} + +.alert-warning>h5 { + color: #f57f17; + border-bottom: 4px solid #f57f17; + background-color: #fff3e0; +} + +.alert-danger>h5 { + color: #d32f2f; + border-bottom: 4px solid #d32f2f; + background-color: #ffebee; +} + +/* CODE HIGHLIGHT */ +pre { +padding: 9.5px; +margin: 0 0 10px; +font-size: 13px; +word-break: break-all; +word-wrap: break-word; +background-color: #fffaef; +border-radius: 4px; +box-shadow: 0px 1px 4px 1px rgba(100, 100, 100, 0.4); +} + +.sideaffix { + overflow: visible; +} \ No newline at end of file From e0ea4ef36ffb6ff5289c34541f006cdbba9394e2 Mon Sep 17 00:00:00 2001 From: Benjamin Carleski Date: Wed, 2 Oct 2019 14:16:03 -0700 Subject: [PATCH 007/393] Add null check in allowed template check --- src/Umbraco.Web/PublishedContentExtensions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index c80f41d9fc..d1bc7dffff 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -135,6 +135,9 @@ namespace Umbraco.Web public static bool IsAllowedTemplate(this IPublishedContent content, int templateId) { + if (content == null) + return false; + if (UmbracoConfig.For.UmbracoSettings().WebRouting.DisableAlternativeTemplates == true) return content.TemplateId == templateId; From 96a2af2653da49ebdc793c9aa53acd54cc173110 Mon Sep 17 00:00:00 2001 From: Rasmus Olofsson Date: Thu, 24 Oct 2019 18:43:37 +0200 Subject: [PATCH 008/393] Add Swedish translations for prompt --- src/Umbraco.Web.UI/umbraco/config/lang/sv.xml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml index 982f31e383..0a2f37ce02 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/sv.xml @@ -182,10 +182,11 @@ Besök - Stay - Discard changes - You have unsaved changes - Are you sure you want to navigate away from this page? - you have unsaved changes + Stanna + Avfärda ändringar + Du har inte sparat dina ändringar + Är du säker på att du vill navigera bort från denna sida? - du har inte sparat dina ändringar + Avpublicering kommer att ta bort denna sida och alla dess ättlingar från hemsidan. Done From 06ca4294b19abdecdb5de433a41539dc5dffe0ef Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 30 Oct 2019 08:52:23 +0100 Subject: [PATCH 009/393] Styling updates to image cropper and upload property editors --- .../src/less/components/umb-property-file-upload.less | 1 + src/Umbraco.Web.UI.Client/src/less/mixins.less | 2 +- src/Umbraco.Web.UI.Client/src/less/property-editors.less | 4 ++++ src/Umbraco.Web.UI.Client/src/less/variables.less | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less index 08b1a1b5e1..9550acfb1b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-file-upload.less @@ -1,6 +1,7 @@ .umb-property-file-upload { .umb-upload-button-big { + max-width: (@propertyEditorLimitedWidth - 40); display: block; padding: 20px; opacity: 1; diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index e49755338b..52638c82dc 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -419,7 +419,7 @@ // -------------------------------------------------- // Limit width of specific property editors .umb-property-editor--limit-width { - max-width: 800px; + max-width: @propertyEditorLimitedWidth; } // Horizontal dividers diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index dc2d402671..b7cf22be62 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -721,6 +721,10 @@ // // File upload // -------------------------------------------------- +.umb-fileupload { + display: flex; +} + .umb-fileupload .preview { border-radius: 5px; border: 1px solid @gray-6; diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 166640829b..a3f2183f83 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -322,6 +322,7 @@ @paddingSmall: 2px 10px; // 26px @paddingMini: 0 6px; // 22px +@propertyEditorLimitedWidth: 800px; // Disabled this to keep consistency throughout the backoffice UI. Untill a better solution is thought up, this will do. @baseBorderRadius: 3px; // 2px; From 8a02b4f58723d2c9746e7a59d417449ff5e2e933 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 30 Oct 2019 11:24:08 +0100 Subject: [PATCH 010/393] Editors should always be allowed to delete their own content (#6799) * Users should always be allowed to delete their own content * Changed comment to be more precise --- src/Umbraco.Web/Editors/ContentController.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 8bb7af0077..bfc9ae071f 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -28,6 +28,7 @@ using Constants = Umbraco.Core.Constants; using umbraco.cms.businesslogic; using System.Collections; using umbraco; +using umbraco.BusinessLogic.Actions; namespace Umbraco.Web.Editors { @@ -1171,6 +1172,12 @@ namespace Umbraco.Web.Editors var path = contentItem != null ? contentItem.Path : nodeId.ToString(); var permission = userService.GetPermissionsForPath(user, path); + // users are allowed to delete their own content - see ContentTreeControllerBase.GetAllowedUserMenuItemsForNode() + if(contentItem != null && contentItem.CreatorId == user.Id) + { + permission.PermissionsSet.Add(new EntityPermission(0, contentItem.Id, new [] { ActionDelete.Instance.Letter.ToString() } )); + } + var allowed = true; foreach (var p in permissionsToCheck) { From f172760f961218760ce4f9e8dc0e9b7908ad90b0 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 30 Oct 2019 11:24:08 +0100 Subject: [PATCH 011/393] Editors should always be allowed to delete their own content (#6799) * Users should always be allowed to delete their own content * Changed comment to be more precise (cherry picked from commit 8a02b4f58723d2c9746e7a59d417449ff5e2e933) --- src/Umbraco.Web/Editors/ContentController.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index 8bb7af0077..bfc9ae071f 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -28,6 +28,7 @@ using Constants = Umbraco.Core.Constants; using umbraco.cms.businesslogic; using System.Collections; using umbraco; +using umbraco.BusinessLogic.Actions; namespace Umbraco.Web.Editors { @@ -1171,6 +1172,12 @@ namespace Umbraco.Web.Editors var path = contentItem != null ? contentItem.Path : nodeId.ToString(); var permission = userService.GetPermissionsForPath(user, path); + // users are allowed to delete their own content - see ContentTreeControllerBase.GetAllowedUserMenuItemsForNode() + if(contentItem != null && contentItem.CreatorId == user.Id) + { + permission.PermissionsSet.Add(new EntityPermission(0, contentItem.Id, new [] { ActionDelete.Instance.Letter.ToString() } )); + } + var allowed = true; foreach (var p in permissionsToCheck) { From d38f275467a84a031e89276557b1e81769d362c4 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Wed, 30 Oct 2019 12:15:32 +0000 Subject: [PATCH 012/393] Media uploader in RTE doesn't select uploaded image --- .../common/overlays/mediaPicker/mediapicker.controller.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index fc62c0ce67..136e035b85 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -251,7 +251,12 @@ angular.module("umbraco") if (files.length === 1 && $scope.model.selectedImages.length === 0) { var image = $scope.images[$scope.images.length - 1]; $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); + // handle both entity and full media object + if (image.image) { + $scope.target.url = image.image; + } else { + $scope.target.url = mediaHelper.resolveFile(image); + } selectImage(image); } }); From 38d241952047f00b8c11ef0662d2b60093dc41f0 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Wed, 30 Oct 2019 12:15:32 +0000 Subject: [PATCH 013/393] Media uploader in RTE doesn't select uploaded image (cherry picked from commit d38f275467a84a031e89276557b1e81769d362c4) --- .../common/overlays/mediaPicker/mediapicker.controller.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js index fc62c0ce67..136e035b85 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.controller.js @@ -251,7 +251,12 @@ angular.module("umbraco") if (files.length === 1 && $scope.model.selectedImages.length === 0) { var image = $scope.images[$scope.images.length - 1]; $scope.target = image; - $scope.target.url = mediaHelper.resolveFile(image); + // handle both entity and full media object + if (image.image) { + $scope.target.url = image.image; + } else { + $scope.target.url = mediaHelper.resolveFile(image); + } selectImage(image); } }); From da3a971906f9c3b455ca912edf106893a31c7f79 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Wed, 30 Oct 2019 18:57:33 +0100 Subject: [PATCH 014/393] Do not show breadcrumb for immediate children in the recycle bin --- .../src/common/directives/components/content/edit.controller.js | 2 +- .../src/views/media/media.edit.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index cab71842b1..d3f65d8f63 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -42,7 +42,7 @@ $scope.page.isNew = Object.toBoolean(newVal); //We fetch all ancestors of the node to generate the footer breadcrumb navigation - if (content.parentId && content.parentId !== -1) { + if (content.parentId && content.parentId !== -1 && content.parentId !== -20) { loadBreadcrumb(); if (!watchingCulture) { $scope.$watch('culture', diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index a5884c2355..b8c38e923b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -245,7 +245,7 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, syncTreeNode($scope.content, data.path, true); } - if ($scope.content.parentId && $scope.content.parentId != -1) { + if ($scope.content.parentId && $scope.content.parentId !== -1 && $scope.content.parentId !== -21) { //We fetch all ancestors of the node to generate the footer breadcrump navigation entityResource.getAncestors(nodeId, "media") .then(function (anc) { From 486dd0b8eaacbface476cac7aa7738aca0b95f61 Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Thu, 31 Oct 2019 11:39:52 +0000 Subject: [PATCH 015/393] PublishedSnapshotService.OnMemberRefreshedEntity stores data as published rather than edited --- .../PublishedCache/NuCache/PublishedSnapshotService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index e76b526492..742841aeb5 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -1218,7 +1218,7 @@ namespace Umbraco.Web.PublishedCache.NuCache var member = args.Entity; // refresh the edited data - OnRepositoryRefreshed(db, member, true); + OnRepositoryRefreshed(db, member, false); } private void OnRepositoryRefreshed(IUmbracoDatabase db, IContentBase content, bool published) From ce969aad3b62ec12a257a4d9cd3566287e88a825 Mon Sep 17 00:00:00 2001 From: Jeffrey Schoemaker Date: Thu, 7 Nov 2019 13:34:26 +0100 Subject: [PATCH 016/393] Make it a bit less complex --- .github/BUILD.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/BUILD.md b/.github/BUILD.md index c6e870f396..ad33872423 100644 --- a/.github/BUILD.md +++ b/.github/BUILD.md @@ -43,6 +43,8 @@ If you only see a build.bat-file, you're probably on the wrong branch. If you sw You might run into [Powershell quirks](#powershell-quirks). +If it runs without errors; Hooray! Now you can continue with [the next step](CONTRIBUTING.md#how-do-i-begin) and open the solution and build it. + ### Build Infrastructure The Umbraco Build infrastructure relies on a PowerShell object. The object can be retrieved with: From b0e2f2d5113d36049a32c9a9be2523f9964b88a4 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 8 Nov 2019 11:51:02 +1100 Subject: [PATCH 017/393] bumps cdf requirements --- build/NuSpecs/UmbracoCms.Core.nuspec | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 9f507b4915..7f180b4ca0 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -29,8 +29,8 @@ - - + + From 09273b6d6fa37a298941eeb06be195276c2a8af2 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 12 Nov 2019 08:53:35 +0100 Subject: [PATCH 018/393] Remove JS error in console when editing document types with Nested Content properties --- .../propertyeditors/nestedcontent/nestedcontent.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index f2a2fc0cc2..6f2b918642 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -690,7 +690,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop } function updatePropertyActionStates() { - copyAllEntriesAction.isDisabled = $scope.model.value.length === 0; + copyAllEntriesAction.isDisabled = !$scope.model.value || !$scope.model.value.length; } $scope.$watch("currentNode", function (newVal) { From 091b3844aded43fe5847977b7ea88c2c04cb8df8 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 12 Nov 2019 20:34:30 +0100 Subject: [PATCH 019/393] Make sure the media picker can survive a logout and subsequent login --- .../infiniteeditors/mediapicker/mediapicker.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js index ba103a2761..a27fc757f7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.controller.js @@ -311,7 +311,7 @@ angular.module("umbraco") // also make sure the node is not trashed if (nodePath.indexOf($scope.startNodeId.toString()) !== -1 && node.trashed === false) { - gotoFolder({ id: $scope.lastOpenedNode, name: "Media", icon: "icon-folder", path: node.path }); + gotoFolder({ id: $scope.lastOpenedNode || $scope.startNodeId, name: "Media", icon: "icon-folder", path: node.path }); return true; } else { gotoFolder({ id: $scope.startNodeId, name: "Media", icon: "icon-folder" }); From 6f67105645e616a8b36dcddb1db7cabd8c9a3c2d Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 11 Dec 2019 09:25:30 +0100 Subject: [PATCH 020/393] Undo removing the MiniProfiler routes if solution is not in debug mode --- .../Profiling/WebProfilerProvider.cs | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/Umbraco.Web/Profiling/WebProfilerProvider.cs b/src/Umbraco.Web/Profiling/WebProfilerProvider.cs index e0dcfcf9b1..ffd1871ecc 100644 --- a/src/Umbraco.Web/Profiling/WebProfilerProvider.cs +++ b/src/Umbraco.Web/Profiling/WebProfilerProvider.cs @@ -1,10 +1,7 @@ using System; -using System.Linq; using System.Threading; using System.Web; -using System.Web.Routing; using StackExchange.Profiling; -using Umbraco.Core.Configuration; namespace Umbraco.Web.Profiling { @@ -27,20 +24,6 @@ namespace Umbraco.Web.Profiling { // booting... _bootPhase = BootPhase.Boot; - - // Remove Mini Profiler routes when not in debug mode - if (GlobalSettings.DebugMode == false) - { - //NOTE: Keep the global fully qualified name, for some reason without it I was getting null refs - var prefix = global::StackExchange.Profiling.MiniProfiler.Settings.RouteBasePath.Replace("~/", string.Empty); - - using (RouteTable.Routes.GetWriteLock()) - { - var routes = RouteTable.Routes.Where(x => x is Route r && r.Url.StartsWith(prefix)).ToList(); - foreach(var r in routes) - RouteTable.Routes.Remove(r); - } - } } /// @@ -135,4 +118,4 @@ namespace Umbraco.Web.Profiling } } } -} +} \ No newline at end of file From c845f5d8c578e6bdf12bc20000b485dd9933bf10 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 11 Dec 2019 09:26:31 +0100 Subject: [PATCH 021/393] Discard MiniProfiler results when not in debug mode --- src/Umbraco.Web/Profiling/WebProfiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Profiling/WebProfiler.cs b/src/Umbraco.Web/Profiling/WebProfiler.cs index fd980db2d1..f44d4876e2 100644 --- a/src/Umbraco.Web/Profiling/WebProfiler.cs +++ b/src/Umbraco.Web/Profiling/WebProfiler.cs @@ -82,7 +82,7 @@ namespace Umbraco.Web.Profiling if (isBootRequest) _provider.EndBootRequest(); if (isBootRequest || ShouldProfile(sender)) - Stop(); + Stop(!GlobalSettings.DebugMode); } private bool ShouldProfile(object sender) From 262a4cba36fee93cff531ab84ad26b4f88d2ea9c Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Wed, 11 Dec 2019 09:30:25 +0100 Subject: [PATCH 022/393] Explicitly set MiniProfiler storage to HttpRuntimeCacheStorage --- src/Umbraco.Web/Profiling/WebProfiler.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Umbraco.Web/Profiling/WebProfiler.cs b/src/Umbraco.Web/Profiling/WebProfiler.cs index f44d4876e2..2fa5639fa7 100644 --- a/src/Umbraco.Web/Profiling/WebProfiler.cs +++ b/src/Umbraco.Web/Profiling/WebProfiler.cs @@ -3,6 +3,7 @@ using System.Threading; using System.Web; using StackExchange.Profiling; using StackExchange.Profiling.SqlFormatters; +using StackExchange.Profiling.Storage; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Logging; @@ -32,6 +33,7 @@ namespace Umbraco.Web.Profiling MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); MiniProfiler.Settings.StackMaxLength = 5000; MiniProfiler.Settings.ProfilerProvider = _provider; + MiniProfiler.Settings.Storage = new HttpRuntimeCacheStorage(TimeSpan.FromMinutes(30)); //Binds to application events to enable the MiniProfiler with a real HttpRequest UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; From 492fb01ad9c3c56d7e1fcdd2cb22f977fdecd835 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Thu, 12 Dec 2019 13:41:40 +0100 Subject: [PATCH 023/393] Don't load languages in treepicker unless they're needed --- .../treepicker/treepicker.controller.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js index 0ff6403761..edb344b0a5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/treepicker/treepicker.controller.js @@ -75,18 +75,20 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", */ function onInit () { - // load languages - languageResource.getAll().then(function (languages) { - vm.languages = languages; + if (vm.showLanguageSelector) { + // load languages + languageResource.getAll().then(function (languages) { + vm.languages = languages; - // set the default language - vm.languages.forEach(function (language) { - if (language.isDefault) { - vm.selectedLanguage = language; - vm.languageSelectorIsOpen = false; - } + // set the default language + vm.languages.forEach(function (language) { + if (language.isDefault) { + vm.selectedLanguage = language; + vm.languageSelectorIsOpen = false; + } + }); }); - }); + } if (vm.treeAlias === "content") { vm.entityType = "Document"; @@ -211,7 +213,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController", if (vm.dataTypeKey) { queryParams["dataTypeKey"] = vm.dataTypeKey; } - + var queryString = $.param(queryParams); //create the query string from the params object if (!queryString) { From a33d1b4fef4fc30144b2b29a0fd1d273a19ca9dd Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 9 Jan 2020 13:04:27 +0100 Subject: [PATCH 024/393] Added UnitTest project --- .../Builders/DictionaryItemBuilder.cs | 10 ++++++ .../Umbraco.Tests.Shared.csproj | 23 +++++++++++++ .../Models/DictionaryItemTests.cs | 18 ++++++++++ .../Umbraco.Tests.UnitTests.csproj | 34 +++++++++++++++++++ src/umbraco.sln | 14 ++++++++ 5 files changed, 99 insertions(+) create mode 100644 src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs new file mode 100644 index 0000000000..0bb16826e8 --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Tests.Shared.Builders +{ + public class DictionaryItemBuilder + { + public object Build() + { + throw new System.NotImplementedException(); + } + } +} diff --git a/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj b/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj new file mode 100644 index 0000000000..65d17b16df --- /dev/null +++ b/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0 + + + + + + + + + + + + + + + + + + + diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs new file mode 100644 index 0000000000..ac3283df5d --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs @@ -0,0 +1,18 @@ +using NUnit.Framework; +using Umbraco.Tests.Shared.Builders; + +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models +{ + [TestFixture] + public class DictionaryItemTests + { + [Test] + public void Can_Deep_Clone() + { + var builder = new DictionaryItemBuilder(); + + var item = builder + .Build(); + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj new file mode 100644 index 0000000000..8dad14e747 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Tests.UnitTests.csproj @@ -0,0 +1,34 @@ + + + + netcoreapp2.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/umbraco.sln b/src/umbraco.sln index c70f10bdb3..e0f20287bd 100644 --- a/src/umbraco.sln +++ b/src/umbraco.sln @@ -110,6 +110,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Infrastructure", "U EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Configuration", "Umbraco.Configuration\Umbraco.Configuration.csproj", "{FBE7C065-DAC0-4025-A78B-63B24D3AB00B}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.UnitTests", "Umbraco.Tests.UnitTests\Umbraco.Tests.UnitTests.csproj", "{9102ABDF-E537-4E46-B525-C9ED4833EED0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Umbraco.Tests.Shared", "Umbraco.Tests.Shared\Umbraco.Tests.Shared.csproj", "{58F1DFC6-5096-4B99-B57B-984F2EEDFADE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -158,6 +162,14 @@ Global {FBE7C065-DAC0-4025-A78B-63B24D3AB00B}.Debug|Any CPU.Build.0 = Debug|Any CPU {FBE7C065-DAC0-4025-A78B-63B24D3AB00B}.Release|Any CPU.ActiveCfg = Release|Any CPU {FBE7C065-DAC0-4025-A78B-63B24D3AB00B}.Release|Any CPU.Build.0 = Release|Any CPU + {9102ABDF-E537-4E46-B525-C9ED4833EED0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9102ABDF-E537-4E46-B525-C9ED4833EED0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9102ABDF-E537-4E46-B525-C9ED4833EED0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9102ABDF-E537-4E46-B525-C9ED4833EED0}.Release|Any CPU.Build.0 = Release|Any CPU + {58F1DFC6-5096-4B99-B57B-984F2EEDFADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {58F1DFC6-5096-4B99-B57B-984F2EEDFADE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {58F1DFC6-5096-4B99-B57B-984F2EEDFADE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {58F1DFC6-5096-4B99-B57B-984F2EEDFADE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -170,6 +182,8 @@ Global {53594E5B-64A2-4545-8367-E3627D266AE8} = {FD962632-184C-4005-A5F3-E705D92FC645} {3A33ADC9-C6C0-4DB1-A613-A9AF0210DF3D} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} {C7311C00-2184-409B-B506-52A5FAEA8736} = {FD962632-184C-4005-A5F3-E705D92FC645} + {9102ABDF-E537-4E46-B525-C9ED4833EED0} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} + {58F1DFC6-5096-4B99-B57B-984F2EEDFADE} = {B5BD12C1-A454-435E-8A46-FF4A364C0382} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {7A0F2E34-D2AF-4DAB-86A0-7D7764B3D0EC} From 997796758e55a736f533c8ed7b72604cb27a7f58 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 9 Jan 2020 14:24:20 +0100 Subject: [PATCH 025/393] Added Builders --- .../Builders/BuilderBase.cs | 7 ++ .../Builders/ChildBuilderBase.cs | 19 +++++ .../Builders/DictionaryItemBuilder.cs | 53 +++++++++++++- .../Builders/DictionaryTranslationBuilder.cs | 71 +++++++++++++++++++ .../Builders/LanguageBuilder.cs | 35 +++++++++ .../Umbraco.Tests.Shared.csproj | 10 +++ .../Models/DictionaryItemTests.cs | 55 +++++++++++++- 7 files changed, 245 insertions(+), 5 deletions(-) create mode 100644 src/Umbraco.Tests.Shared/Builders/BuilderBase.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/ChildBuilderBase.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs diff --git a/src/Umbraco.Tests.Shared/Builders/BuilderBase.cs b/src/Umbraco.Tests.Shared/Builders/BuilderBase.cs new file mode 100644 index 0000000000..d026999351 --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/BuilderBase.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Shared.Builders +{ + public abstract class BuilderBase + { + public abstract T Build(); + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/ChildBuilderBase.cs b/src/Umbraco.Tests.Shared/Builders/ChildBuilderBase.cs new file mode 100644 index 0000000000..742b86d16e --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/ChildBuilderBase.cs @@ -0,0 +1,19 @@ +namespace Umbraco.Tests.Shared.Builders +{ + public abstract class ChildBuilderBase : BuilderBase + { + private readonly TParent _parentBuilder; + + protected ChildBuilderBase(TParent parentBuilder) + { + _parentBuilder = parentBuilder; + } + + + public TParent Done() + { + return _parentBuilder; + } + + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs index 0bb16826e8..64f74417a4 100644 --- a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs @@ -1,10 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Umbraco.Core.Models; + namespace Umbraco.Tests.Shared.Builders { public class DictionaryItemBuilder { - public object Build() + private string _itemkey = null; + private readonly List _translationBuilders = new List(); + private DateTime? _createDate; + private DateTime? _updateDate; + private int? _id = null; + + + public DictionaryItem Build() { - throw new System.NotImplementedException(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var id = _id ?? 1; + + var result = new DictionaryItem(_itemkey ?? Guid.NewGuid().ToString()); + result.Translations = _translationBuilders.Select(x => x.Build()); + result.CreateDate = createDate; + result.UpdateDate = updateDate; + result.Id = id; + return result; + } + + public DictionaryTranslationBuilder AddTranslation() + { + var builder = new DictionaryTranslationBuilder(this); + + _translationBuilders.Add(builder); + + return builder; + } + + public DictionaryItemBuilder WithCreateData(DateTime createDate) + { + _createDate = createDate; + return this; + } + + public DictionaryItemBuilder WithUpdateData(DateTime updateDate) + { + _updateDate = updateDate; + return this; + } + + public DictionaryItemBuilder WithId(int id) + { + _id = id; + return this; } } } diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs new file mode 100644 index 0000000000..fb82133030 --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs @@ -0,0 +1,71 @@ +using System; +using System.Globalization; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Shared.Builders +{ + public class DictionaryTranslationBuilder : ChildBuilderBase + { + private string _value = null; + private Guid? _uniqueId = null; + private DateTime? _createDate; + private DateTime? _updateDate; + + + private LanguageBuilder _languageBuilder; + private int? _id = null; + + public DictionaryTranslationBuilder(DictionaryItemBuilder parentBuilder) : base(parentBuilder) + { + _languageBuilder = new LanguageBuilder(this); + + } + + public override IDictionaryTranslation Build() + { + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var id = _id ?? 1; + + var result = new DictionaryTranslation( + _languageBuilder.Build(), + _value ?? Guid.NewGuid().ToString(), + _uniqueId ?? Guid.NewGuid()); + + result.CreateDate = createDate; + result.UpdateDate = updateDate; + result.Id = id; + + return result; + } + + public LanguageBuilder WithLanguage() + { + return _languageBuilder; + } + + public DictionaryTranslationBuilder WithValue(string value) + { + _value = value; + return this; + } + + public DictionaryTranslationBuilder WithCreateData(DateTime createDate) + { + _createDate = createDate; + return this; + } + + public DictionaryTranslationBuilder WithUpdateData(DateTime updateDate) + { + _updateDate = updateDate; + return this; + } + + public DictionaryTranslationBuilder WithId(int id) + { + _id = id; + return this; + } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs new file mode 100644 index 0000000000..3adc6262a2 --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs @@ -0,0 +1,35 @@ +using System.Globalization; +using Moq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Shared.Builders +{ + public class LanguageBuilder : ChildBuilderBase + { + private int? _id = null; + private string _isoCode = null; + + public LanguageBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + public override ILanguage Build() + { + var culture = CultureInfo.GetCultureInfo("en-US"); + var isoCode = _isoCode ?? culture.Name; + return new Language(Mock.Of(), isoCode) + { + Id = _id ?? 1, + CultureName = culture.TwoLetterISOLanguageName, + IsoCode = new RegionInfo(culture.LCID).Name, + }; + } + + public LanguageBuilder WithId(int id) + { + _id = id; + return this; + } + } +} diff --git a/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj b/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj index 65d17b16df..93afb6f531 100644 --- a/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj +++ b/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj @@ -18,6 +18,16 @@ + + + + + + + + + + diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs index ac3283df5d..57ac00c026 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs @@ -1,4 +1,7 @@ +using System.Linq; +using Newtonsoft.Json; using NUnit.Framework; +using Umbraco.Core.Models; using Umbraco.Tests.Shared.Builders; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models @@ -6,13 +9,59 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models [TestFixture] public class DictionaryItemTests { + private readonly DictionaryItemBuilder _builder = new DictionaryItemBuilder(); + [Test] public void Can_Deep_Clone() { - var builder = new DictionaryItemBuilder(); - - var item = builder + var item = _builder + .AddTranslation() + .WithValue("Colour") + .Done() + .AddTranslation() + .WithValue("Color") + .Done() .Build(); + + var clone = (DictionaryItem)item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.ItemKey, item.ItemKey); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.ParentId, item.ParentId); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + Assert.AreEqual(clone.Translations.Count(), item.Translations.Count()); + for (var i = 0; i < item.Translations.Count(); i++) + { + Assert.AreNotSame(clone.Translations.ElementAt(i), item.Translations.ElementAt(i)); + Assert.AreEqual(clone.Translations.ElementAt(i), item.Translations.ElementAt(i)); + } + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + + } + + [Test] + public void Can_Serialize_Without_Error() + { + var item = _builder + .AddTranslation() + .WithValue("Colour") + .Done() + .AddTranslation() + .WithValue("Color") + .Done() + .Build(); + + Assert.DoesNotThrow(() => JsonConvert.SerializeObject(item)); } } } From 9d6d43c2d0e66a1ca5b9a31947ed8d76d99c4f7e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 9 Jan 2020 14:49:11 +0100 Subject: [PATCH 026/393] Added language tests --- .../Builders/DictionaryItemBuilder.cs | 9 ++++ .../Builders/LanguageBuilder.cs | 8 ++++ .../Models/DictionaryItemTests.cs | 14 +----- .../Models/LanguageTests.cs | 44 +++++++++++++++++++ 4 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/LanguageTests.cs diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs index 64f74417a4..992c197854 100644 --- a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs @@ -55,5 +55,14 @@ namespace Umbraco.Tests.Shared.Builders _id = id; return this; } + + public DictionaryItemBuilder WithRandomTranslations(int count) + { + for (var i = 0; i < count; i++) + { + AddTranslation().Done(); + } + return this; + } } } diff --git a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs index 3adc6262a2..82375434a6 100644 --- a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs @@ -5,6 +5,14 @@ using Umbraco.Core.Models; namespace Umbraco.Tests.Shared.Builders { + public class LanguageBuilder : LanguageBuilder + { + public LanguageBuilder() : base(null) + { + } + } + + public class LanguageBuilder : ChildBuilderBase { private int? _id = null; diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs index 57ac00c026..85c0e56089 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DictionaryItemTests.cs @@ -15,12 +15,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models public void Can_Deep_Clone() { var item = _builder - .AddTranslation() - .WithValue("Colour") - .Done() - .AddTranslation() - .WithValue("Color") - .Done() + .WithRandomTranslations(2) .Build(); var clone = (DictionaryItem)item.DeepClone(); @@ -53,12 +48,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models public void Can_Serialize_Without_Error() { var item = _builder - .AddTranslation() - .WithValue("Colour") - .Done() - .AddTranslation() - .WithValue("Color") - .Done() + .WithRandomTranslations(2) .Build(); Assert.DoesNotThrow(() => JsonConvert.SerializeObject(item)); diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/LanguageTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/LanguageTests.cs new file mode 100644 index 0000000000..57ed20310a --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/LanguageTests.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Tests.Shared.Builders; + +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models +{ + [TestFixture] + public class LanguageTests + { + private readonly LanguageBuilder _builder = new LanguageBuilder(); + + [Test] + public void Can_Deep_Clone() + { + var item = _builder.Build(); + + var clone = (Language) item.DeepClone(); + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.CreateDate, item.CreateDate); + Assert.AreEqual(clone.CultureName, item.CultureName); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.IsoCode, item.IsoCode); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var item = _builder.Build(); + + Assert.DoesNotThrow(() => JsonConvert.SerializeObject(item)); + } + } +} From 728799b036d751d12f0d5828a11d5ec360da8a1d Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 9 Jan 2020 15:32:19 +0100 Subject: [PATCH 027/393] using extension methods to enforce consistency --- .../Builders/DataEditorBuilder.cs | 28 ++++++++++ .../Builders/DataTypeBuilder.cs | 33 +++++++++++ .../Builders/DictionaryItemBuilder.cs | 40 +++++++------ .../Builders/DictionaryTranslationBuilder.cs | 21 ++++--- .../Builders/Extensions/BuilderExtensions.cs | 28 ++++++++++ .../Builders/LanguageBuilder.cs | 11 ++-- .../Markers/IWithCreateDateBuilder.cs | 9 +++ .../Builders/Markers/IWithIdBuilder.cs | 7 +++ .../Markers/IWithUpdateDateBuilder.cs | 9 +++ .../Models/DataTypeTests.cs | 56 +++++++++++++++++++ 10 files changed, 205 insertions(+), 37 deletions(-) create mode 100644 src/Umbraco.Tests.Shared/Builders/DataEditorBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/Markers/IWithCreateDateBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/Markers/IWithIdBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/Markers/IWithUpdateDateBuilder.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs diff --git a/src/Umbraco.Tests.Shared/Builders/DataEditorBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DataEditorBuilder.cs new file mode 100644 index 0000000000..5a0b18cf9e --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/DataEditorBuilder.cs @@ -0,0 +1,28 @@ +using Moq; +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; + +namespace Umbraco.Tests.Shared.Builders +{ + public class DataEditorBuilder : ChildBuilderBase + { + public DataEditorBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + public override IDataEditor Build() + { + var result = new DataEditor( + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of() + ); + + return result; + } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs new file mode 100644 index 0000000000..128990ab4e --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs @@ -0,0 +1,33 @@ +using Umbraco.Core.Models; + +namespace Umbraco.Tests.Shared.Builders +{ + public class DataTypeBuilder : BuilderBase, IWithIdBuilder + { + private readonly DataEditorBuilder _dataEditorBuilder; + private int? _id; + + public DataTypeBuilder() + { + _dataEditorBuilder = new DataEditorBuilder(this); + } + + public override DataType Build() + { + var editor = _dataEditorBuilder.Build(); + var id = _id ?? 1; + var result = new DataType(editor) + { + Id = id + }; + + return result; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs index 992c197854..5f88a636f7 100644 --- a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs @@ -1,12 +1,11 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Linq; using Umbraco.Core.Models; namespace Umbraco.Tests.Shared.Builders { - public class DictionaryItemBuilder + public class DictionaryItemBuilder : IWithIdBuilder, IWithCreateDateBuilder, IWithUpdateDateBuilder { private string _itemkey = null; private readonly List _translationBuilders = new List(); @@ -14,7 +13,6 @@ namespace Umbraco.Tests.Shared.Builders private DateTime? _updateDate; private int? _id = null; - public DictionaryItem Build() { var createDate = _createDate ?? DateTime.Now; @@ -38,24 +36,6 @@ namespace Umbraco.Tests.Shared.Builders return builder; } - public DictionaryItemBuilder WithCreateData(DateTime createDate) - { - _createDate = createDate; - return this; - } - - public DictionaryItemBuilder WithUpdateData(DateTime updateDate) - { - _updateDate = updateDate; - return this; - } - - public DictionaryItemBuilder WithId(int id) - { - _id = id; - return this; - } - public DictionaryItemBuilder WithRandomTranslations(int count) { for (var i = 0; i < count; i++) @@ -64,5 +44,23 @@ namespace Umbraco.Tests.Shared.Builders } return this; } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } } } diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs index fb82133030..3f471777c4 100644 --- a/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs @@ -1,10 +1,9 @@ using System; -using System.Globalization; using Umbraco.Core.Models; namespace Umbraco.Tests.Shared.Builders { - public class DictionaryTranslationBuilder : ChildBuilderBase + public class DictionaryTranslationBuilder : ChildBuilderBase, IWithIdBuilder, IWithCreateDateBuilder, IWithUpdateDateBuilder { private string _value = null; private Guid? _uniqueId = null; @@ -50,22 +49,22 @@ namespace Umbraco.Tests.Shared.Builders return this; } - public DictionaryTranslationBuilder WithCreateData(DateTime createDate) + int? IWithIdBuilder.Id { - _createDate = createDate; - return this; + get => _id; + set => _id = value; } - public DictionaryTranslationBuilder WithUpdateData(DateTime updateDate) + DateTime? IWithCreateDateBuilder.CreateDate { - _updateDate = updateDate; - return this; + get => _createDate; + set => _createDate = value; } - public DictionaryTranslationBuilder WithId(int id) + DateTime? IWithUpdateDateBuilder.UpdateDate { - _id = id; - return this; + get => _updateDate; + set => _updateDate = value; } } } diff --git a/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs new file mode 100644 index 0000000000..9b6a961c4f --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs @@ -0,0 +1,28 @@ +using System; + +namespace Umbraco.Tests.Shared.Builders.Extensions +{ + public static class BuilderExtensions + { + public static T WithId(this T builder, int id) + where T : IWithIdBuilder + { + builder.Id = id; + return builder; + } + + public static T WithCreateDate(this T builder, DateTime createDate) + where T : IWithCreateDateBuilder + { + builder.CreateDate = createDate; + return builder; + } + + public static T WithUpdateDate(this T builder, DateTime updateDate) + where T : IWithUpdateDateBuilder + { + builder.UpdateDate = updateDate; + return builder; + } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs index 82375434a6..d3855145f3 100644 --- a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs @@ -13,10 +13,11 @@ namespace Umbraco.Tests.Shared.Builders } - public class LanguageBuilder : ChildBuilderBase + public class LanguageBuilder : ChildBuilderBase, IWithIdBuilder { - private int? _id = null; + private string _isoCode = null; + private int? _id; public LanguageBuilder(TParent parentBuilder) : base(parentBuilder) { @@ -34,10 +35,10 @@ namespace Umbraco.Tests.Shared.Builders }; } - public LanguageBuilder WithId(int id) + int? IWithIdBuilder.Id { - _id = id; - return this; + get => _id; + set => _id = value; } } } diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithCreateDateBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Markers/IWithCreateDateBuilder.cs new file mode 100644 index 0000000000..3ea3b9e7cc --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/Markers/IWithCreateDateBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Shared.Builders +{ + public interface IWithCreateDateBuilder + { + DateTime? CreateDate { get; set; } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithIdBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Markers/IWithIdBuilder.cs new file mode 100644 index 0000000000..7575d2429d --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/Markers/IWithIdBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Shared.Builders +{ + public interface IWithIdBuilder + { + int? Id { get; set; } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithUpdateDateBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Markers/IWithUpdateDateBuilder.cs new file mode 100644 index 0000000000..a0d4fe6258 --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/Markers/IWithUpdateDateBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Shared.Builders +{ + public interface IWithUpdateDateBuilder + { + DateTime? UpdateDate { get; set; } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs new file mode 100644 index 0000000000..ecd4249bc6 --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -0,0 +1,56 @@ +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Tests.Shared.Builders; +using Umbraco.Tests.Shared.Builders.Extensions; + +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models +{ + [TestFixture] + public class DataTypeTests + { + + private readonly DataTypeBuilder _builder = new DataTypeBuilder(); + [Test] + public void Can_Deep_Clone() + { + var dtd = _builder + .WithId(3123) + .Build(); + + var clone = (DataType) dtd.DeepClone(); + + Assert.AreNotSame(clone, dtd); + Assert.AreEqual(clone, dtd); + Assert.AreEqual(clone.CreateDate, dtd.CreateDate); + Assert.AreEqual(clone.CreatorId, dtd.CreatorId); + Assert.AreEqual(clone.DatabaseType, dtd.DatabaseType); + Assert.AreEqual(clone.Id, dtd.Id); + Assert.AreEqual(clone.Key, dtd.Key); + Assert.AreEqual(clone.Level, dtd.Level); + Assert.AreEqual(clone.Name, dtd.Name); + Assert.AreEqual(clone.ParentId, dtd.ParentId); + Assert.AreEqual(clone.Path, dtd.Path); + Assert.AreEqual(clone.SortOrder, dtd.SortOrder); + Assert.AreEqual(clone.Trashed, dtd.Trashed); + Assert.AreEqual(clone.UpdateDate, dtd.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(dtd, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var item = _builder + .Build(); + + Assert.DoesNotThrow(() => JsonConvert.SerializeObject(item)); + } + + } +} From b786dd185b63c24ed63ee7ee6173922ad2b1d6bf Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 9 Jan 2020 15:48:28 +0100 Subject: [PATCH 028/393] Clean up --- .../Builders/Extensions/BuilderExtensions.cs | 2 +- .../Builders/{Markers => Interfaces}/IWithCreateDateBuilder.cs | 0 .../Builders/{Markers => Interfaces}/IWithIdBuilder.cs | 0 .../Builders/{Markers => Interfaces}/IWithUpdateDateBuilder.cs | 0 .../Umbraco.Infrastructure/Models/DataTypeTests.cs | 1 - 5 files changed, 1 insertion(+), 2 deletions(-) rename src/Umbraco.Tests.Shared/Builders/{Markers => Interfaces}/IWithCreateDateBuilder.cs (100%) rename src/Umbraco.Tests.Shared/Builders/{Markers => Interfaces}/IWithIdBuilder.cs (100%) rename src/Umbraco.Tests.Shared/Builders/{Markers => Interfaces}/IWithUpdateDateBuilder.cs (100%) diff --git a/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs index 9b6a961c4f..bab15761c1 100644 --- a/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Tests.Shared.Builders.Extensions +namespace Umbraco.Tests.Shared.Builders { public static class BuilderExtensions { diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithCreateDateBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithCreateDateBuilder.cs similarity index 100% rename from src/Umbraco.Tests.Shared/Builders/Markers/IWithCreateDateBuilder.cs rename to src/Umbraco.Tests.Shared/Builders/Interfaces/IWithCreateDateBuilder.cs diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithIdBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithIdBuilder.cs similarity index 100% rename from src/Umbraco.Tests.Shared/Builders/Markers/IWithIdBuilder.cs rename to src/Umbraco.Tests.Shared/Builders/Interfaces/IWithIdBuilder.cs diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithUpdateDateBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithUpdateDateBuilder.cs similarity index 100% rename from src/Umbraco.Tests.Shared/Builders/Markers/IWithUpdateDateBuilder.cs rename to src/Umbraco.Tests.Shared/Builders/Interfaces/IWithUpdateDateBuilder.cs diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs index ecd4249bc6..d845e1b4a0 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -2,7 +2,6 @@ using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.Shared.Builders; -using Umbraco.Tests.Shared.Builders.Extensions; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { From 76ed4ccc6c9838de3fea106cb6c5ec2c05bfde83 Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 9 Jan 2020 16:37:24 +0100 Subject: [PATCH 029/393] Updating namespaces --- .../Builders/DataTypeBuilder.cs | 1 + .../Builders/DictionaryItemBuilder.cs | 1 + .../Builders/DictionaryTranslationBuilder.cs | 1 + .../Builders/Extensions/BuilderExtensions.cs | 15 +++++++++++++++ .../Builders/LanguageBuilder.cs | 1 + .../Builders/Markers/IWithCreateDateBuilder.cs | 2 +- .../Builders/Markers/IWithIdBuilder.cs | 2 +- .../Builders/Markers/IWithUpdateDateBuilder.cs | 2 +- 8 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs index 128990ab4e..b22b313a75 100644 --- a/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs @@ -1,4 +1,5 @@ using Umbraco.Core.Models; +using Umbraco.Tests.Shared.Builders.Markers; namespace Umbraco.Tests.Shared.Builders { diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs index 5f88a636f7..d978980e2a 100644 --- a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models; +using Umbraco.Tests.Shared.Builders.Markers; namespace Umbraco.Tests.Shared.Builders { diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs index 3f471777c4..d74b0b1730 100644 --- a/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs @@ -1,5 +1,6 @@ using System; using Umbraco.Core.Models; +using Umbraco.Tests.Shared.Builders.Markers; namespace Umbraco.Tests.Shared.Builders { diff --git a/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs index 9b6a961c4f..a307e89cb0 100644 --- a/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs @@ -1,4 +1,5 @@ using System; +using Umbraco.Tests.Shared.Builders.Markers; namespace Umbraco.Tests.Shared.Builders.Extensions { @@ -24,5 +25,19 @@ namespace Umbraco.Tests.Shared.Builders.Extensions builder.UpdateDate = updateDate; return builder; } + + public static T WithAlias(this T builder, string alias) + where T : IWithAliasBuilder + { + builder.Alias = alias; + return builder; + } + + public static T WithName(this T builder, string name) + where T : IWithNameBuilder + { + builder.Name = name; + return builder; + } } } diff --git a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs index d3855145f3..f4ec69541d 100644 --- a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs @@ -2,6 +2,7 @@ using System.Globalization; using Moq; using Umbraco.Core.Configuration; using Umbraco.Core.Models; +using Umbraco.Tests.Shared.Builders.Markers; namespace Umbraco.Tests.Shared.Builders { diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithCreateDateBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Markers/IWithCreateDateBuilder.cs index 3ea3b9e7cc..6bc3c8c887 100644 --- a/src/Umbraco.Tests.Shared/Builders/Markers/IWithCreateDateBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/Markers/IWithCreateDateBuilder.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Tests.Shared.Builders +namespace Umbraco.Tests.Shared.Builders.Markers { public interface IWithCreateDateBuilder { diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithIdBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Markers/IWithIdBuilder.cs index 7575d2429d..da93d9ac1f 100644 --- a/src/Umbraco.Tests.Shared/Builders/Markers/IWithIdBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/Markers/IWithIdBuilder.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Tests.Shared.Builders +namespace Umbraco.Tests.Shared.Builders.Markers { public interface IWithIdBuilder { diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithUpdateDateBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Markers/IWithUpdateDateBuilder.cs index a0d4fe6258..5ced57fa4e 100644 --- a/src/Umbraco.Tests.Shared/Builders/Markers/IWithUpdateDateBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/Markers/IWithUpdateDateBuilder.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Tests.Shared.Builders +namespace Umbraco.Tests.Shared.Builders.Markers { public interface IWithUpdateDateBuilder { From 3dbe0c2847e4b0ea405eb5ce380f9922aa5273cd Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 9 Jan 2020 16:38:31 +0100 Subject: [PATCH 030/393] Fixing indentation --- .../Umbraco.Infrastructure/Models/DataTypeTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs index ecd4249bc6..ce984b3a79 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/DataTypeTests.cs @@ -46,8 +46,7 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models [Test] public void Can_Serialize_Without_Error() { - var item = _builder - .Build(); + var item = _builder.Build(); Assert.DoesNotThrow(() => JsonConvert.SerializeObject(item)); } From 0748fede869a3a9b93a8267abc0c0ef8a20f066a Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 9 Jan 2020 16:39:28 +0100 Subject: [PATCH 031/393] Adding tests for RelationType --- .../Builders/Markers/IWithAliasBuilder.cs | 7 +++ .../Builders/Markers/IWithNameBuilder.cs | 7 +++ .../Builders/RelationTypeBuilder.cs | 61 +++++++++++++++++++ .../Models/RelationTypeTests.cs | 50 +++++++++++++++ 4 files changed, 125 insertions(+) create mode 100644 src/Umbraco.Tests.Shared/Builders/Markers/IWithAliasBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/Markers/IWithNameBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs create mode 100644 src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithAliasBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Markers/IWithAliasBuilder.cs new file mode 100644 index 0000000000..1a754ccaaf --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/Markers/IWithAliasBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Shared.Builders.Markers +{ + public interface IWithAliasBuilder + { + string Alias { get; set; } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/Markers/IWithNameBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Markers/IWithNameBuilder.cs new file mode 100644 index 0000000000..1170e7c446 --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/Markers/IWithNameBuilder.cs @@ -0,0 +1,7 @@ +namespace Umbraco.Tests.Shared.Builders.Markers +{ + public interface IWithNameBuilder + { + string Name { get; set; } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs b/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs new file mode 100644 index 0000000000..c00690650d --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs @@ -0,0 +1,61 @@ +using System; +using Umbraco.Core.Models; +using Umbraco.Tests.Shared.Builders.Markers; + +namespace Umbraco.Tests.Shared.Builders +{ + + public class RelationTypeBuilder : RelationTypeBuilder + { + public RelationTypeBuilder() : base(null) + { + } + } + + public class RelationTypeBuilder : ChildBuilderBase, IWithIdBuilder, IWithAliasBuilder, IWithNameBuilder + { + private int? _id; + private string _alias; + private string _name; + private readonly Guid? _parentObjectType = null; + private readonly Guid? _childObjectType = null; + + public RelationTypeBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + + public override IRelationType Build() + { + var alias = _alias ?? Guid.NewGuid().ToString(); + var name = _name ?? Guid.NewGuid().ToString(); + var parentObjectType = _parentObjectType ?? Guid.NewGuid(); + var childObjectType = _childObjectType ?? Guid.NewGuid(); + var id = _id ?? 1; + + return new RelationType(name, alias, false, parentObjectType, + childObjectType) + { + Id = id + }; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + string IWithAliasBuilder.Alias + { + get => _alias; + set => _alias = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } + } +} diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs new file mode 100644 index 0000000000..da255926df --- /dev/null +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json; +using NUnit.Framework; +using Umbraco.Core.Models; +using Umbraco.Tests.Shared.Builders; + +namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models +{ + [TestFixture] + public class RelationTypeTests + { + private readonly RelationTypeBuilder _builder = new RelationTypeBuilder(); + + [Test] + public void Can_Deep_Clone() + { + var item = _builder.Build(); + + var clone = (RelationType) item.DeepClone(); + + Assert.AreNotSame(clone, item); + Assert.AreEqual(clone, item); + Assert.AreEqual(clone.Alias, item.Alias); + Assert.AreEqual(clone.ChildObjectType, item.ChildObjectType); + Assert.AreEqual(clone.IsBidirectional, item.IsBidirectional); + Assert.AreEqual(clone.Id, item.Id); + Assert.AreEqual(clone.Key, item.Key); + Assert.AreEqual(clone.Name, item.Name); + Assert.AreNotSame(clone.ParentObjectType, item.ParentObjectType); + Assert.AreEqual(clone.UpdateDate, item.UpdateDate); + + //This double verifies by reflection + var allProps = clone.GetType().GetProperties(); + foreach (var propertyInfo in allProps) + { + Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(item, null)); + } + } + + [Test] + public void Can_Serialize_Without_Error() + { + var item = _builder.Build(); + + Assert.DoesNotThrow(() => JsonConvert.SerializeObject(item)); + } + } +} From 7656072bd576b45128f275ab2f1189405d698a8a Mon Sep 17 00:00:00 2001 From: elitsa Date: Thu, 9 Jan 2020 16:45:46 +0100 Subject: [PATCH 032/393] Fixing namespaces and usings --- src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs | 2 +- src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs | 2 +- .../Builders/DictionaryTranslationBuilder.cs | 2 +- .../Builders/Extensions/BuilderExtensions.cs | 2 +- .../Builders/Interfaces/IWithAliasBuilder.cs | 2 +- .../Builders/Interfaces/IWithCreateDateBuilder.cs | 2 +- .../Builders/Interfaces/IWithIdBuilder.cs | 2 +- .../Builders/Interfaces/IWithNameBuilder.cs | 2 +- .../Builders/Interfaces/IWithUpdateDateBuilder.cs | 2 +- src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs | 2 +- src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs | 2 +- .../Umbraco.Infrastructure/Models/RelationTypeTests.cs | 5 +---- 12 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs index b22b313a75..ed225d53bd 100644 --- a/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs @@ -1,5 +1,5 @@ using Umbraco.Core.Models; -using Umbraco.Tests.Shared.Builders.Markers; +using Umbraco.Tests.Shared.Builders.Interfaces; namespace Umbraco.Tests.Shared.Builders { diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs index d978980e2a..97837f6783 100644 --- a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models; -using Umbraco.Tests.Shared.Builders.Markers; +using Umbraco.Tests.Shared.Builders.Interfaces; namespace Umbraco.Tests.Shared.Builders { diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs index d74b0b1730..5d44488bab 100644 --- a/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs @@ -1,6 +1,6 @@ using System; using Umbraco.Core.Models; -using Umbraco.Tests.Shared.Builders.Markers; +using Umbraco.Tests.Shared.Builders.Interfaces; namespace Umbraco.Tests.Shared.Builders { diff --git a/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs index a307e89cb0..0180e9e54c 100644 --- a/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs @@ -1,5 +1,5 @@ using System; -using Umbraco.Tests.Shared.Builders.Markers; +using Umbraco.Tests.Shared.Builders.Interfaces; namespace Umbraco.Tests.Shared.Builders.Extensions { diff --git a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithAliasBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithAliasBuilder.cs index 1a754ccaaf..3ab70123f4 100644 --- a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithAliasBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithAliasBuilder.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Tests.Shared.Builders.Markers +namespace Umbraco.Tests.Shared.Builders.Interfaces { public interface IWithAliasBuilder { diff --git a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithCreateDateBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithCreateDateBuilder.cs index 6bc3c8c887..7a7b69fc95 100644 --- a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithCreateDateBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithCreateDateBuilder.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Tests.Shared.Builders.Markers +namespace Umbraco.Tests.Shared.Builders.Interfaces { public interface IWithCreateDateBuilder { diff --git a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithIdBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithIdBuilder.cs index da93d9ac1f..a5a6175d8f 100644 --- a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithIdBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithIdBuilder.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Tests.Shared.Builders.Markers +namespace Umbraco.Tests.Shared.Builders.Interfaces { public interface IWithIdBuilder { diff --git a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithNameBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithNameBuilder.cs index 1170e7c446..2ddcf65333 100644 --- a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithNameBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithNameBuilder.cs @@ -1,4 +1,4 @@ -namespace Umbraco.Tests.Shared.Builders.Markers +namespace Umbraco.Tests.Shared.Builders.Interfaces { public interface IWithNameBuilder { diff --git a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithUpdateDateBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithUpdateDateBuilder.cs index 5ced57fa4e..88a23d0275 100644 --- a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithUpdateDateBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithUpdateDateBuilder.cs @@ -1,6 +1,6 @@ using System; -namespace Umbraco.Tests.Shared.Builders.Markers +namespace Umbraco.Tests.Shared.Builders.Interfaces { public interface IWithUpdateDateBuilder { diff --git a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs index f4ec69541d..4b196e169c 100644 --- a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs @@ -2,7 +2,7 @@ using System.Globalization; using Moq; using Umbraco.Core.Configuration; using Umbraco.Core.Models; -using Umbraco.Tests.Shared.Builders.Markers; +using Umbraco.Tests.Shared.Builders.Interfaces; namespace Umbraco.Tests.Shared.Builders { diff --git a/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs b/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs index c00690650d..55ec30fd77 100644 --- a/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs @@ -1,6 +1,6 @@ using System; using Umbraco.Core.Models; -using Umbraco.Tests.Shared.Builders.Markers; +using Umbraco.Tests.Shared.Builders.Interfaces; namespace Umbraco.Tests.Shared.Builders { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs index da255926df..4054f9d992 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Newtonsoft.Json; +using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.Shared.Builders; From ccc6f3877820daf4e57494d76bfd4959dea35f3c Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Mon, 13 Jan 2020 09:15:02 +1000 Subject: [PATCH 033/393] guard code for non-infinite editing to prevent route change --- .../src/views/media/media.edit.controller.js | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js index a5884c2355..2ed43f7495 100644 --- a/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/media/media.edit.controller.js @@ -73,14 +73,14 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, var content = $scope.content; - // we need to check wether an app is present in the current data, if not we will present the default app. + // we need to check whether an app is present in the current data, if not we will present the default app. var isAppPresent = false; // on first init, we dont have any apps. but if we are re-initializing, we do, but ... if ($scope.app) { // lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.) - _.forEach(content.apps, function(app) { + content.apps.forEach(app => { if (app === $scope.app) { isAppPresent = true; } @@ -88,7 +88,7 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, // if we did reload our DocType, but still have the same app we will try to find it by the alias. if (isAppPresent === false) { - _.forEach(content.apps, function(app) { + content.apps.forEach(app => { if (app.alias === $scope.app.alias) { isAppPresent = true; app.active = true; @@ -182,24 +182,26 @@ function mediaEditController($scope, $routeParams, $q, appState, mediaResource, formHelper.resetForm({ scope: $scope }); - contentEditingHelper.handleSuccessfulSave({ - scope: $scope, - savedContent: data, - rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) - }); - - editorState.set($scope.content); - - syncTreeNode($scope.content, data.path); - - init(); - - $scope.page.saveButtonState = "success"; - // close the editor if it's infinite mode + // submit function manages rebinding changes if(infiniteMode && $scope.model.submit) { $scope.model.mediaNode = $scope.content; $scope.model.submit($scope.model); + } else { + // if not infinite mode, rebind changed props etc + contentEditingHelper.handleSuccessfulSave({ + scope: $scope, + savedContent: data, + rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data) + }); + + editorState.set($scope.content); + + syncTreeNode($scope.content, data.path); + + $scope.page.saveButtonState = "success"; + + init(); } }, function(err) { From 21aa8fd95921f44d07064738a3c838775895d366 Mon Sep 17 00:00:00 2001 From: Nathan Woulfe Date: Mon, 13 Jan 2020 09:15:27 +1000 Subject: [PATCH 034/393] remove lodash in favor of native es6 methods, adds type attribute to buttons --- .../mediapicker/mediapicker.controller.js | 121 +++++++++--------- .../mediapicker/mediapicker.html | 6 +- 2 files changed, 60 insertions(+), 67 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js index c9d4caf312..ec6ab05cbd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.controller.js @@ -48,49 +48,45 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // This is done by remapping the int/guid ids into a new array of items, where we create "Deleted item" placeholders // when there is no match for a selected id. This will ensure that the values being set on save, are the same as before. - medias = _.map(ids, - function (id) { - var found = _.find(medias, - function (m) { - // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and - // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() - // compares and be completely sure it works. - return m.udi.toString() === id.toString() || m.id.toString() === id.toString(); - }); - if (found) { - return found; - } else { - return { - name: vm.labels.deletedItem, - id: $scope.model.config.idType !== "udi" ? id : null, - udi: $scope.model.config.idType === "udi" ? id : null, - icon: "icon-picture", - thumbnail: null, - trashed: true - }; - } - }); + medias = ids.map(id => { + var found = medias.find(m => + // We could use coercion (two ='s) here .. but not sure if this works equally well in all browsers and + // it's prone to someone "fixing" it at some point without knowing the effects. Rather use toString() + // compares and be completely sure it works. + m.udi.toString() === id.toString() || m.id.toString() === id.toString()); + + if (found) { + return found; + } else { + return { + name: vm.labels.deletedItem, + id: $scope.model.config.idType !== "udi" ? id : null, + udi: $scope.model.config.idType === "udi" ? id : null, + icon: "icon-picture", + thumbnail: null, + trashed: true + }; + } + }); - _.each(medias, - function (media, i) { + medias.forEach(media => { + if (!media.extension && media.id && media.metaData) { + media.extension = mediaHelper.getFileExtension(media.metaData.MediaPath); + } - if (!media.extension && media.id && media.metaData) { - media.extension = mediaHelper.getFileExtension(media.metaData.MediaPath); - } + // if there is no thumbnail, try getting one if the media is not a placeholder item + if (!media.thumbnail && media.id && media.metaData) { + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + } - // if there is no thumbnail, try getting one if the media is not a placeholder item - if (!media.thumbnail && media.id && media.metaData) { - media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); - } + $scope.mediaItems.push(media); - $scope.mediaItems.push(media); - - if ($scope.model.config.idType === "udi") { - $scope.ids.push(media.udi); - } else { - $scope.ids.push(media.id); - } - }); + if ($scope.model.config.idType === "udi") { + $scope.ids.push(media.udi); + } else { + $scope.ids.push(media.id); + } + }); sync(); }); @@ -100,7 +96,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl function sync() { $scope.model.value = $scope.ids.join(); removeAllEntriesAction.isDisabled = $scope.ids.length === 0; - }; + } function setDirty() { angularHelper.getCurrentForm($scope).$setDirty(); @@ -111,18 +107,17 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // reload. We only reload the images that is already picked but has been updated. // We have to get the entities from the server because the media // can be edited without being selected - _.each($scope.images, - function (image, i) { - if (updatedMediaNodes.indexOf(image.udi) !== -1) { - image.loading = true; - entityResource.getById(image.udi, "media") - .then(function (mediaEntity) { - angular.extend(image, mediaEntity); - image.thumbnail = mediaHelper.resolveFileFromEntity(image, true); - image.loading = false; - }); - } - }); + $scope.mediaItems.forEach(media => { + if (updatedMediaNodes.indexOf(media.udi) !== -1) { + media.loading = true; + entityResource.getById(media.udi, "Media") + .then(function (mediaEntity) { + angular.extend(media, mediaEntity); + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); + media.loading = false; + }); + } + }); } function init() { @@ -177,20 +172,20 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl // the media picker is using media entities so we get the // entity so we easily can format it for use in the media grid if (model && model.mediaNode) { - entityResource.getById(model.mediaNode.id, "media") + entityResource.getById(model.mediaNode.id, "Media") .then(function (mediaEntity) { // if an image is selecting more than once // we need to update all the media items - angular.forEach($scope.images, function (image) { - if (image.id === model.mediaNode.id) { - angular.extend(image, mediaEntity); - image.thumbnail = mediaHelper.resolveFileFromEntity(image, true); + $scope.mediaItems.forEach(media => { + if (media.id === model.mediaNode.id) { + angular.extend(media, mediaEntity); + media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); } }); }); } }, - close: function (model) { + close: function () { editorService.close(); } }; @@ -210,7 +205,7 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl editorService.close(); - _.each(model.selection, function (media, i) { + model.selection.forEach(media => { // if there is no thumbnail, try getting one if the media is not a placeholder item if (!media.thumbnail && media.id && media.metaData) { media.thumbnail = mediaHelper.resolveFileFromEntity(media, true); @@ -280,16 +275,14 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl disabled: !multiPicker, items: "li:not(.add-wrapper)", cancel: ".unsortable", - update: function (e, ui) { + update: function () { setDirty(); $timeout(function() { // TODO: Instead of doing this with a timeout would be better to use a watch like we do in the // content picker. Then we don't have to worry about setting ids, render models, models, we just set one and let the // watch do all the rest. - $scope.ids = _.map($scope.mediaItems, - function (item) { - return $scope.model.config.idType === "udi" ? item.udi : item.id; - }); + $scope.ids = $scope.mediaItems.map(media => $scope.model.config.idType === "udi" ? media.udi : media.id); + sync(); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index 1f9bd4e3c0..b4da03d30b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -36,16 +36,16 @@
- -
  • -
  • From 2b0e9e8450d5f39d0a7e9fcc0c4b4e1303eb0859 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Mon, 13 Jan 2020 07:29:12 +0100 Subject: [PATCH 035/393] Added more options on the test builders --- .../Builders/ConfigurationEditorBuilder.cs | 33 +++ .../Builders/DataEditorBuilder.cs | 34 ++- .../Builders/DataTypeBuilder.cs | 138 ++++++++++- .../Builders/DataValueEditorBuilder.cs | 66 ++++++ .../Builders/DictionaryItemBuilder.cs | 105 +++++--- .../Builders/DictionaryTranslationBuilder.cs | 103 ++++---- .../Builders/Extensions/BuilderExtensions.cs | 7 + .../Builders/GlobalSettingsBuilder.cs | 224 ++++++++++++++++++ .../Interfaces/IWithCultureInfoBuilder.cs | 9 + .../Interfaces/IWithDeleteDateBuilder.cs | 9 + .../Builders/Interfaces/IWithKeyBuilder.cs | 9 + .../Builders/LanguageBuilder.cs | 101 +++++++- .../Builders/RelationTypeBuilder.cs | 116 ++++++--- .../Umbraco.Tests.Shared.csproj | 1 + .../Models/LanguageTests.cs | 1 + .../Models/RelationTypeTests.cs | 10 +- 16 files changed, 842 insertions(+), 124 deletions(-) create mode 100644 src/Umbraco.Tests.Shared/Builders/ConfigurationEditorBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/DataValueEditorBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/GlobalSettingsBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/Interfaces/IWithCultureInfoBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/Interfaces/IWithDeleteDateBuilder.cs create mode 100644 src/Umbraco.Tests.Shared/Builders/Interfaces/IWithKeyBuilder.cs diff --git a/src/Umbraco.Tests.Shared/Builders/ConfigurationEditorBuilder.cs b/src/Umbraco.Tests.Shared/Builders/ConfigurationEditorBuilder.cs new file mode 100644 index 0000000000..3abf683bf2 --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/ConfigurationEditorBuilder.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Tests.Shared.Builders +{ + public class ConfigurationEditorBuilder : ChildBuilderBase + { + private IDictionary _defaultConfiguration; + + + public ConfigurationEditorBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + + public ConfigurationEditorBuilder WithDefaultConfiguration(IDictionary defaultConfiguration) + { + _defaultConfiguration = defaultConfiguration; + return this; + } + + public override IConfigurationEditor Build() + { + var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + + return new ConfigurationEditor() + { + DefaultConfiguration = defaultConfiguration, + }; + } + + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/DataEditorBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DataEditorBuilder.cs index 5a0b18cf9e..0a9e94a74e 100644 --- a/src/Umbraco.Tests.Shared/Builders/DataEditorBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DataEditorBuilder.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using Moq; using Umbraco.Core.Logging; using Umbraco.Core.PropertyEditors; @@ -8,21 +9,46 @@ namespace Umbraco.Tests.Shared.Builders { public class DataEditorBuilder : ChildBuilderBase { + private readonly ConfigurationEditorBuilder> _explicitConfigurationEditorBuilder; + private readonly DataValueEditorBuilder> _explicitValueEditorBuilder; + private IDictionary _defaultConfiguration; + public DataEditorBuilder(TParent parentBuilder) : base(parentBuilder) { + _explicitConfigurationEditorBuilder = new ConfigurationEditorBuilder>(this); + _explicitValueEditorBuilder = new DataValueEditorBuilder>(this); } + public DataEditorBuilder WithDefaultConfiguration(IDictionary defaultConfiguration) + { + _defaultConfiguration = defaultConfiguration; + return this; + } + + public ConfigurationEditorBuilder> AddExplicitConfigurationEditorBuilder() => + _explicitConfigurationEditorBuilder; + + public DataValueEditorBuilder> AddExplicitValueEditorBuilder() => + _explicitValueEditorBuilder; + public override IDataEditor Build() { - var result = new DataEditor( + var defaultConfiguration = _defaultConfiguration ?? new Dictionary(); + var explicitConfigurationEditor = _explicitConfigurationEditorBuilder.Build(); + var explicitValueEditor = _explicitValueEditorBuilder.Build(); + + return new DataEditor( Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of() - ); - - return result; + ) + { + DefaultConfiguration = defaultConfiguration, + ExplicitConfigurationEditor = explicitConfigurationEditor, + ExplicitValueEditor = explicitValueEditor + }; } } } diff --git a/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs index ed225d53bd..62e9e35ee0 100644 --- a/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DataTypeBuilder.cs @@ -1,28 +1,124 @@ +using System; using Umbraco.Core.Models; using Umbraco.Tests.Shared.Builders.Interfaces; namespace Umbraco.Tests.Shared.Builders { - public class DataTypeBuilder : BuilderBase, IWithIdBuilder + public class DataTypeBuilder + : BuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithDeleteDateBuilder, + IWithNameBuilder { private readonly DataEditorBuilder _dataEditorBuilder; private int? _id; + private int? _parentId; + private Guid? _key; + private DateTime? _createDate; + private DateTime? _updateDate; + private DateTime? _deleteDate; + private string _name; + private bool? _trashed; + // private object _configuration; + private int? _level; + private string _path; + private int? _creatorId; + private ValueStorageType? _databaseType; + private int? _sortOrder; public DataTypeBuilder() { _dataEditorBuilder = new DataEditorBuilder(this); } + public DataTypeBuilder WithParentId(int parentId) + { + _parentId = parentId; + return this; + } + + public DataTypeBuilder WithTrashed(bool trashed) + { + _trashed = trashed; + return this; + } + + // public DataTypeBuilder WithConfiguration(object configuration) + // { + // _configuration = configuration; + // return this; + // } + + public DataTypeBuilder WithLevel(int level) + { + _level = level; + return this; + } + + public DataTypeBuilder WithPath(string path) + { + _path = path; + return this; + } + + public DataTypeBuilder WithCreatorId(int creatorId) + { + _creatorId = creatorId; + return this; + } + + public DataTypeBuilder WithDatabaseType(ValueStorageType databaseType) + { + _databaseType = databaseType; + return this; + } + + public DataTypeBuilder WithSortOrder(int sortOrder) + { + _sortOrder = sortOrder; + return this; + } + + public DataEditorBuilder AddEditor() + { + return _dataEditorBuilder; + } + public override DataType Build() { var editor = _dataEditorBuilder.Build(); + var parentId = _parentId ?? -1; var id = _id ?? 1; - var result = new DataType(editor) - { - Id = id - }; + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var deleteDate = _deleteDate ?? null; + var name = _name ?? Guid.NewGuid().ToString(); + // var configuration = _configuration ?? editor.GetConfigurationEditor().DefaultConfigurationObject; + var level = _level ?? 0; + var path = _path ?? string.Empty; + var creatorId = _creatorId ?? 1; + var databaseType = _databaseType ?? ValueStorageType.Ntext; + var sortOrder = _sortOrder ?? 0; - return result; + return new DataType(editor, parentId) + { + Id = id, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + DeleteDate = deleteDate, + Name = name, + Trashed = _trashed ?? false, + Level = level, + Path = path, + CreatorId = creatorId, + DatabaseType = databaseType, + SortOrder = sortOrder, + }; } int? IWithIdBuilder.Id @@ -30,5 +126,35 @@ namespace Umbraco.Tests.Shared.Builders get => _id; set => _id = value; } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + DateTime? IWithDeleteDateBuilder.DeleteDate + { + get => _deleteDate; + set => _deleteDate = value; + } + + string IWithNameBuilder.Name + { + get => _name; + set => _name = value; + } } } diff --git a/src/Umbraco.Tests.Shared/Builders/DataValueEditorBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DataValueEditorBuilder.cs new file mode 100644 index 0000000000..be26510934 --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/DataValueEditorBuilder.cs @@ -0,0 +1,66 @@ +using System; +using Moq; +using Umbraco.Core.PropertyEditors; +using Umbraco.Core.Services; +using Umbraco.Core.Strings; + +namespace Umbraco.Tests.Shared.Builders +{ + public class DataValueEditorBuilder : ChildBuilderBase + { + private string _configuration; + private string _view; + private bool? _hideLabel; + private string _valueType; + + + public DataValueEditorBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + public DataValueEditorBuilder WithConfiguration(string configuration) + { + _configuration = configuration; + return this; + } + + public DataValueEditorBuilder WithView(string view) + { + _view = view; + return this; + } + + public DataValueEditorBuilder WithHideLabel(bool hideLabel) + { + _hideLabel = hideLabel; + return this; + } + + public DataValueEditorBuilder WithValueType(string valueType) + { + _valueType = valueType; + return this; + } + + public override IDataValueEditor Build() + { + var configuration = _configuration ?? null; + var view = _view ?? null; + var hideLabel = _hideLabel ?? false; + var valueType = _valueType ?? Guid.NewGuid().ToString(); + + return new DataValueEditor( + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of() + ) + { + Configuration = configuration, + View = view, + HideLabel = hideLabel, + ValueType = valueType, + }; + } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs index 97837f6783..6baff80972 100644 --- a/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryItemBuilder.cs @@ -6,28 +6,90 @@ using Umbraco.Tests.Shared.Builders.Interfaces; namespace Umbraco.Tests.Shared.Builders { - public class DictionaryItemBuilder : IWithIdBuilder, IWithCreateDateBuilder, IWithUpdateDateBuilder + public class DictionaryItemBuilder + : BuilderBase, + IWithIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithDeleteDateBuilder, + IWithKeyBuilder { - private string _itemkey = null; - private readonly List _translationBuilders = new List(); - private DateTime? _createDate; - private DateTime? _updateDate; - private int? _id = null; + private readonly List _translationBuilders = + new List(); - public DictionaryItem Build() + private DateTime? _createDate; + private DateTime? _deleteDate; + private int? _id; + private string _itemKey; + private Guid? _key; + private Guid? _parentId; + private DateTime? _updateDate; + + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithDeleteDateBuilder.DeleteDate + { + get => _deleteDate; + set => _deleteDate = value; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + public override DictionaryItem Build() { var createDate = _createDate ?? DateTime.Now; var updateDate = _updateDate ?? DateTime.Now; + var deleteDate = _deleteDate ?? null; var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var parentId = _parentId ?? null; + var itemKey = _itemKey ?? Guid.NewGuid().ToString(); - var result = new DictionaryItem(_itemkey ?? Guid.NewGuid().ToString()); - result.Translations = _translationBuilders.Select(x => x.Build()); - result.CreateDate = createDate; - result.UpdateDate = updateDate; - result.Id = id; + var result = new DictionaryItem(itemKey) + { + Translations = _translationBuilders.Select(x => x.Build()), + CreateDate = createDate, + UpdateDate = updateDate, + DeleteDate = deleteDate, + Id = id, + ParentId = parentId, + Key = key, + }; return result; } + public DictionaryItemBuilder WithParentId(Guid parentId) + { + _parentId = parentId; + return this; + } + + public DictionaryItemBuilder WithItemKey(string itemKey) + { + _itemKey = itemKey; + return this; + } + public DictionaryTranslationBuilder AddTranslation() { var builder = new DictionaryTranslationBuilder(this); @@ -43,25 +105,8 @@ namespace Umbraco.Tests.Shared.Builders { AddTranslation().Done(); } + return this; } - - int? IWithIdBuilder.Id - { - get => _id; - set => _id = value; - } - - DateTime? IWithCreateDateBuilder.CreateDate - { - get => _createDate; - set => _createDate = value; - } - - DateTime? IWithUpdateDateBuilder.UpdateDate - { - get => _updateDate; - set => _updateDate = value; - } } } diff --git a/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs b/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs index 5d44488bab..bd596383d1 100644 --- a/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/DictionaryTranslationBuilder.cs @@ -4,56 +4,27 @@ using Umbraco.Tests.Shared.Builders.Interfaces; namespace Umbraco.Tests.Shared.Builders { - public class DictionaryTranslationBuilder : ChildBuilderBase, IWithIdBuilder, IWithCreateDateBuilder, IWithUpdateDateBuilder + public class DictionaryTranslationBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithDeleteDateBuilder, + IWithKeyBuilder { - private string _value = null; - private Guid? _uniqueId = null; + private readonly LanguageBuilder _languageBuilder; + private readonly Guid? _uniqueId = null; private DateTime? _createDate; + private DateTime? _deleteDate; + private int? _id; + private Guid? _key; private DateTime? _updateDate; + private string _value; - private LanguageBuilder _languageBuilder; - private int? _id = null; - public DictionaryTranslationBuilder(DictionaryItemBuilder parentBuilder) : base(parentBuilder) { _languageBuilder = new LanguageBuilder(this); - - } - - public override IDictionaryTranslation Build() - { - var createDate = _createDate ?? DateTime.Now; - var updateDate = _updateDate ?? DateTime.Now; - var id = _id ?? 1; - - var result = new DictionaryTranslation( - _languageBuilder.Build(), - _value ?? Guid.NewGuid().ToString(), - _uniqueId ?? Guid.NewGuid()); - - result.CreateDate = createDate; - result.UpdateDate = updateDate; - result.Id = id; - - return result; - } - - public LanguageBuilder WithLanguage() - { - return _languageBuilder; - } - - public DictionaryTranslationBuilder WithValue(string value) - { - _value = value; - return this; - } - - int? IWithIdBuilder.Id - { - get => _id; - set => _id = value; } DateTime? IWithCreateDateBuilder.CreateDate @@ -62,10 +33,58 @@ namespace Umbraco.Tests.Shared.Builders set => _createDate = value; } + DateTime? IWithDeleteDateBuilder.DeleteDate + { + get => _deleteDate; + set => _deleteDate = value; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + DateTime? IWithUpdateDateBuilder.UpdateDate { get => _updateDate; set => _updateDate = value; } + + public override IDictionaryTranslation Build() + { + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var deleteDate = _deleteDate ?? null; + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + + var result = new DictionaryTranslation( + _languageBuilder.Build(), + _value ?? Guid.NewGuid().ToString(), + _uniqueId ?? key) + { + CreateDate = createDate, + UpdateDate = updateDate, + DeleteDate = deleteDate, + Id = id + }; + + return result; + } + + public LanguageBuilder AddLanguage() => _languageBuilder; + + public DictionaryTranslationBuilder WithValue(string value) + { + _value = value; + return this; + } } } diff --git a/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs b/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs index 0180e9e54c..702bb19bb2 100644 --- a/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs +++ b/src/Umbraco.Tests.Shared/Builders/Extensions/BuilderExtensions.cs @@ -39,5 +39,12 @@ namespace Umbraco.Tests.Shared.Builders.Extensions builder.Name = name; return builder; } + + public static T WithKey(this T builder, Guid key) + where T : IWithKeyBuilder + { + builder.Key = key; + return builder; + } } } diff --git a/src/Umbraco.Tests.Shared/Builders/GlobalSettingsBuilder.cs b/src/Umbraco.Tests.Shared/Builders/GlobalSettingsBuilder.cs new file mode 100644 index 0000000000..b3c8e90fa5 --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/GlobalSettingsBuilder.cs @@ -0,0 +1,224 @@ +using Umbraco.Core.Configuration; + +namespace Umbraco.Tests.Shared.Builders +{ + public class GlobalSettingsBuilder : GlobalSettingsBuilder + { + public GlobalSettingsBuilder() : base(null) + { + } + } + + public class GlobalSettingsBuilder : ChildBuilderBase + + { + private string _configurationStatus; + private string _databaseFactoryServerVersion; + private string _defaultUiLanguage; + private bool? _disableElectionForSingleServer; + private bool? _hideTopLevelNodeFromPath; + private bool? _installEmptyDatabase; + private bool? _installMissingDatabase; + private bool? _isSmtpServerConfigured; + private string _path; + private string _registerType; + private string _reservedPaths; + private string _reservedUrls; + private int? _timeOutInMinutes; + private string _umbracoCssPath; + private string _umbracoMediaPath; + private string _umbracoPath; + private string _umbracoScriptsPath; + private bool? _useHttps; + private int? _versionCheckPeriod; + + + public GlobalSettingsBuilder(TParent parentBuilder) : base(parentBuilder) + { + } + + public GlobalSettingsBuilder WithConfigurationStatus(string configurationStatus) + { + _configurationStatus = configurationStatus; + return this; + } + + public GlobalSettingsBuilder WithDatabaseFactoryServerVersion(string databaseFactoryServerVersion) + { + _databaseFactoryServerVersion = databaseFactoryServerVersion; + return this; + } + + public GlobalSettingsBuilder WithDefaultUiLanguage(string defaultUiLanguage) + { + _defaultUiLanguage = defaultUiLanguage; + return this; + } + + public GlobalSettingsBuilder WithDisableElectionForSingleServer(bool disableElectionForSingleServer) + { + _disableElectionForSingleServer = disableElectionForSingleServer; + return this; + } + + public GlobalSettingsBuilder WithHideTopLevelNodeFromPath(bool hideTopLevelNodeFromPath) + { + _hideTopLevelNodeFromPath = hideTopLevelNodeFromPath; + return this; + } + + public GlobalSettingsBuilder WithInstallEmptyDatabase(bool installEmptyDatabase) + { + _installEmptyDatabase = installEmptyDatabase; + return this; + } + + public GlobalSettingsBuilder WithInstallMissingDatabase(bool installMissingDatabase) + { + _installMissingDatabase = installMissingDatabase; + return this; + } + + public GlobalSettingsBuilder WithIsSmtpServerConfigured(bool isSmtpServerConfigured) + { + _isSmtpServerConfigured = isSmtpServerConfigured; + return this; + } + + public GlobalSettingsBuilder WithPath(string path) + { + _path = path; + return this; + } + + public GlobalSettingsBuilder WithRegisterType(string registerType) + { + _registerType = registerType; + return this; + } + + public GlobalSettingsBuilder WithReservedPaths(string reservedPaths) + { + _reservedPaths = reservedPaths; + return this; + } + + public GlobalSettingsBuilder WithReservedUrls(string reservedUrls) + { + _reservedUrls = reservedUrls; + return this; + } + + public GlobalSettingsBuilder WithUmbracoPath(string umbracoPath) + { + _umbracoPath = umbracoPath; + return this; + } + + public GlobalSettingsBuilder WithUseHttps(bool useHttps) + { + _useHttps = useHttps; + return this; + } + + public GlobalSettingsBuilder WithUmbracoCssPath(string umbracoCssPath) + { + _umbracoCssPath = umbracoCssPath; + return this; + } + + public GlobalSettingsBuilder WithUmbracoMediaPath(string umbracoMediaPath) + { + _umbracoMediaPath = umbracoMediaPath; + return this; + } + + public GlobalSettingsBuilder WithUmbracoScriptsPath(string umbracoScriptsPath) + { + _umbracoScriptsPath = umbracoScriptsPath; + return this; + } + + public GlobalSettingsBuilder WithVersionCheckPeriod(int versionCheckPeriod) + { + _versionCheckPeriod = versionCheckPeriod; + return this; + } + + public GlobalSettingsBuilder WithTimeOutInMinutes(int timeOutInMinutes) + { + _timeOutInMinutes = timeOutInMinutes; + return this; + } + + public override IGlobalSettings Build() + { + var configurationStatus = _configurationStatus ?? "9.0.0"; + var databaseFactoryServerVersion = _databaseFactoryServerVersion ?? null; + var defaultUiLanguage = _defaultUiLanguage ?? "en"; + var disableElectionForSingleServer = _disableElectionForSingleServer ?? false; + var hideTopLevelNodeFromPath = _hideTopLevelNodeFromPath ?? false; + var installEmptyDatabase = _installEmptyDatabase ?? false; + var installMissingDatabase = _installMissingDatabase ?? false; + var isSmtpServerConfigured = _isSmtpServerConfigured ?? false; + var path = _path ?? "/umbraco"; + var registerType = _registerType ?? null; + var reservedPaths = _reservedPaths ?? "~/app_plugins/,~/install/,~/mini-profiler-resources/,"; + var reservedUrls = _reservedUrls ?? "~/config/splashes/noNodes.aspx,~/.well-known,"; + var umbracoPath = _umbracoPath ?? "~/umbraco"; + var useHttps = _useHttps ?? false; + var umbracoCssPath = _umbracoCssPath ?? "~/css"; + var umbracoMediaPath = _umbracoMediaPath ?? "~/media"; + var umbracoScriptsPath = _umbracoScriptsPath ?? "~/scripts"; + var versionCheckPeriod = _versionCheckPeriod ?? 0; + var timeOutInMinutes = _timeOutInMinutes ?? 20; + + + return new TestGlobalSettings + { + ConfigurationStatus = configurationStatus, + DatabaseFactoryServerVersion = databaseFactoryServerVersion, + DefaultUILanguage = defaultUiLanguage, + DisableElectionForSingleServer = disableElectionForSingleServer, + HideTopLevelNodeFromPath = hideTopLevelNodeFromPath, + InstallEmptyDatabase = installEmptyDatabase, + InstallMissingDatabase = installMissingDatabase, + IsSmtpServerConfigured = isSmtpServerConfigured, + Path = path, + RegisterType = registerType, + ReservedPaths = reservedPaths, + ReservedUrls = reservedUrls, + UmbracoPath = umbracoPath, + UseHttps = useHttps, + UmbracoCssPath = umbracoCssPath, + UmbracoMediaPath = umbracoMediaPath, + UmbracoScriptsPath = umbracoScriptsPath, + VersionCheckPeriod = versionCheckPeriod, + TimeOutInMinutes = timeOutInMinutes + }; + } + + private class TestGlobalSettings : IGlobalSettings + { + public string ReservedUrls { get; set; } + public string ReservedPaths { get; set; } + public string Path { get; set; } + public string ConfigurationStatus { get; set; } + public int TimeOutInMinutes { get; set; } + public string DefaultUILanguage { get; set; } + public bool HideTopLevelNodeFromPath { get; set; } + public bool UseHttps { get; set; } + public int VersionCheckPeriod { get; set; } + public string UmbracoPath { get; set; } + public string UmbracoCssPath { get; set; } + public string UmbracoScriptsPath { get; set; } + public string UmbracoMediaPath { get; set; } + public bool IsSmtpServerConfigured { get; set; } + public bool InstallMissingDatabase { get; set; } + public bool InstallEmptyDatabase { get; set; } + public bool DisableElectionForSingleServer { get; set; } + public string RegisterType { get; set; } + public string DatabaseFactoryServerVersion { get; set; } + } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithCultureInfoBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithCultureInfoBuilder.cs new file mode 100644 index 0000000000..ee33d4549d --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithCultureInfoBuilder.cs @@ -0,0 +1,9 @@ +using System.Globalization; + +namespace Umbraco.Tests.Shared.Builders.Interfaces +{ + public interface IWithCultureInfoBuilder + { + CultureInfo CultureInfo { get; set; } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithDeleteDateBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithDeleteDateBuilder.cs new file mode 100644 index 0000000000..3ae8ddfeed --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithDeleteDateBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Shared.Builders.Interfaces +{ + public interface IWithDeleteDateBuilder + { + DateTime? DeleteDate { get; set; } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithKeyBuilder.cs b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithKeyBuilder.cs new file mode 100644 index 0000000000..78da4dbc0b --- /dev/null +++ b/src/Umbraco.Tests.Shared/Builders/Interfaces/IWithKeyBuilder.cs @@ -0,0 +1,9 @@ +using System; + +namespace Umbraco.Tests.Shared.Builders.Interfaces +{ + public interface IWithKeyBuilder + { + Guid? Key { get; set; } + } +} diff --git a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs index 4b196e169c..59e859dd3a 100644 --- a/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/LanguageBuilder.cs @@ -1,3 +1,4 @@ +using System; using System.Globalization; using Moq; using Umbraco.Core.Configuration; @@ -13,27 +14,45 @@ namespace Umbraco.Tests.Shared.Builders } } - - public class LanguageBuilder : ChildBuilderBase, IWithIdBuilder + public class LanguageBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithKeyBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithDeleteDateBuilder, + IWithCultureInfoBuilder { - - private string _isoCode = null; + private DateTime? _createDate; + private CultureInfo _cultureInfo; + private DateTime? _deleteDate; + private int? _fallbackLanguageId; private int? _id; + private bool? _isDefault; + private bool? _isMandatory; + private Guid? _key; + private DateTime? _updateDate; public LanguageBuilder(TParent parentBuilder) : base(parentBuilder) { } - public override ILanguage Build() + DateTime? IWithCreateDateBuilder.CreateDate { - var culture = CultureInfo.GetCultureInfo("en-US"); - var isoCode = _isoCode ?? culture.Name; - return new Language(Mock.Of(), isoCode) - { - Id = _id ?? 1, - CultureName = culture.TwoLetterISOLanguageName, - IsoCode = new RegionInfo(culture.LCID).Name, - }; + get => _createDate; + set => _createDate = value; + } + + CultureInfo IWithCultureInfoBuilder.CultureInfo + { + get => _cultureInfo; + set => _cultureInfo = value; + } + + DateTime? IWithDeleteDateBuilder.DeleteDate + { + get => _deleteDate; + set => _deleteDate = value; } int? IWithIdBuilder.Id @@ -41,5 +60,61 @@ namespace Umbraco.Tests.Shared.Builders get => _id; set => _id = value; } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + public override ILanguage Build() + { + var cultureInfo = _cultureInfo ?? CultureInfo.GetCultureInfo("en-US"); + var key = _key ?? Guid.NewGuid(); + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var deleteDate = _deleteDate ?? null; + var fallbackLanguageId = _fallbackLanguageId ?? null; + var isDefault = _isDefault ?? false; + var isMandatory = _isMandatory ?? false; + + return new Language(Mock.Of(), cultureInfo.Name) + { + Id = _id ?? 1, + CultureName = cultureInfo.TwoLetterISOLanguageName, + IsoCode = new RegionInfo(cultureInfo.LCID).Name, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + DeleteDate = deleteDate, + IsDefault = isDefault, + IsMandatory = isMandatory, + FallbackLanguageId = fallbackLanguageId + }; + } + + public LanguageBuilder WithIsDefault(bool isDefault) + { + _isDefault = isDefault; + return this; + } + + public LanguageBuilder WithIsMandatory(bool isMandatory) + { + _isMandatory = isMandatory; + return this; + } + + public LanguageBuilder WithFallbackLanguageId(int fallbackLanguageId) + { + _fallbackLanguageId = fallbackLanguageId; + return this; + } } } diff --git a/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs b/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs index 55ec30fd77..2cb81688b7 100644 --- a/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs +++ b/src/Umbraco.Tests.Shared/Builders/RelationTypeBuilder.cs @@ -4,7 +4,6 @@ using Umbraco.Tests.Shared.Builders.Interfaces; namespace Umbraco.Tests.Shared.Builders { - public class RelationTypeBuilder : RelationTypeBuilder { public RelationTypeBuilder() : base(null) @@ -12,39 +11,30 @@ namespace Umbraco.Tests.Shared.Builders } } - public class RelationTypeBuilder : ChildBuilderBase, IWithIdBuilder, IWithAliasBuilder, IWithNameBuilder + public class RelationTypeBuilder + : ChildBuilderBase, + IWithIdBuilder, + IWithAliasBuilder, + IWithNameBuilder, + IWithKeyBuilder, + IWithCreateDateBuilder, + IWithUpdateDateBuilder, + IWithDeleteDateBuilder { - private int? _id; private string _alias; + private Guid? _childObjectType; + private DateTime? _createDate; + private DateTime? _deleteDate; + private int? _id; + private bool? _isBidirectional; + private Guid? _key; private string _name; - private readonly Guid? _parentObjectType = null; - private readonly Guid? _childObjectType = null; + private Guid? _parentObjectType; + private DateTime? _updateDate; public RelationTypeBuilder(TParent parentBuilder) : base(parentBuilder) { } - - - public override IRelationType Build() - { - var alias = _alias ?? Guid.NewGuid().ToString(); - var name = _name ?? Guid.NewGuid().ToString(); - var parentObjectType = _parentObjectType ?? Guid.NewGuid(); - var childObjectType = _childObjectType ?? Guid.NewGuid(); - var id = _id ?? 1; - - return new RelationType(name, alias, false, parentObjectType, - childObjectType) - { - Id = id - }; - } - - int? IWithIdBuilder.Id - { - get => _id; - set => _id = value; - } string IWithAliasBuilder.Alias { @@ -52,10 +42,82 @@ namespace Umbraco.Tests.Shared.Builders set => _alias = value; } + DateTime? IWithCreateDateBuilder.CreateDate + { + get => _createDate; + set => _createDate = value; + } + + DateTime? IWithDeleteDateBuilder.DeleteDate + { + get => _deleteDate; + set => _deleteDate = value; + } + + int? IWithIdBuilder.Id + { + get => _id; + set => _id = value; + } + + Guid? IWithKeyBuilder.Key + { + get => _key; + set => _key = value; + } + string IWithNameBuilder.Name { get => _name; set => _name = value; } + + DateTime? IWithUpdateDateBuilder.UpdateDate + { + get => _updateDate; + set => _updateDate = value; + } + + public override IRelationType Build() + { + var alias = _alias ?? Guid.NewGuid().ToString(); + var name = _name ?? Guid.NewGuid().ToString(); + var parentObjectType = _parentObjectType ?? null; + var childObjectType = _childObjectType ?? null; + var id = _id ?? 1; + var key = _key ?? Guid.NewGuid(); + var isBidirectional = _isBidirectional ?? false; + var createDate = _createDate ?? DateTime.Now; + var updateDate = _updateDate ?? DateTime.Now; + var deleteDate = _deleteDate ?? null; + + return new RelationType(name, alias, isBidirectional, parentObjectType, + childObjectType) + { + Id = id, + Key = key, + CreateDate = createDate, + UpdateDate = updateDate, + DeleteDate = deleteDate + }; + } + + public RelationTypeBuilder WithIsBidirectional(bool isBidirectional) + { + _isBidirectional = isBidirectional; + return this; + } + + public RelationTypeBuilder WithChildObjectType(Guid childObjectType) + { + _childObjectType = childObjectType; + return this; + } + + public RelationTypeBuilder WithParentObjectType(Guid parentObjectType) + { + _parentObjectType = parentObjectType; + return this; + } } } diff --git a/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj b/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj index 93afb6f531..d18779a4fd 100644 --- a/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj +++ b/src/Umbraco.Tests.Shared/Umbraco.Tests.Shared.csproj @@ -18,6 +18,7 @@ + diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/LanguageTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/LanguageTests.cs index 57ed20310a..6d80eac16f 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/LanguageTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/LanguageTests.cs @@ -2,6 +2,7 @@ using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.Shared.Builders; +using Umbraco.Tests.Shared.Builders.Extensions; namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models { diff --git a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs index 4054f9d992..140b974b61 100644 --- a/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs +++ b/src/Umbraco.Tests.UnitTests/Umbraco.Infrastructure/Models/RelationTypeTests.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System; +using Newtonsoft.Json; using NUnit.Framework; using Umbraco.Core.Models; using Umbraco.Tests.Shared.Builders; @@ -13,7 +14,10 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models [Test] public void Can_Deep_Clone() { - var item = _builder.Build(); + var item = _builder + .WithParentObjectType(Guid.NewGuid()) + .WithChildObjectType(Guid.NewGuid()) + .Build(); var clone = (RelationType) item.DeepClone(); @@ -21,11 +25,13 @@ namespace Umbraco.Tests.UnitTests.Umbraco.Infrastructure.Models Assert.AreEqual(clone, item); Assert.AreEqual(clone.Alias, item.Alias); Assert.AreEqual(clone.ChildObjectType, item.ChildObjectType); + Assert.AreEqual(clone.ParentObjectType, item.ParentObjectType); Assert.AreEqual(clone.IsBidirectional, item.IsBidirectional); Assert.AreEqual(clone.Id, item.Id); Assert.AreEqual(clone.Key, item.Key); Assert.AreEqual(clone.Name, item.Name); Assert.AreNotSame(clone.ParentObjectType, item.ParentObjectType); + Assert.AreNotSame(clone.ChildObjectType, item.ChildObjectType); Assert.AreEqual(clone.UpdateDate, item.UpdateDate); //This double verifies by reflection From d4e6eb2b6b052b79d34d095f2405b21f15618ede Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Thu, 16 Jan 2020 17:04:38 +0100 Subject: [PATCH 036/393] AB4084 - Backported/reimplemented fix for contentservice returning outdated results --- .../Cache/DistributedCacheExtensions.cs | 930 ++++---- src/Umbraco.Web/Cache/MediaCacheRefresher.cs | 390 +-- .../Cache/UnpublishedPageCacheRefresher.cs | 46 +- .../XmlPublishedCache/PublishedMediaCache.cs | 2086 ++++++++--------- src/Umbraco.Web/Search/ExamineEvents.cs | 1465 ++++++------ 5 files changed, 2482 insertions(+), 2435 deletions(-) diff --git a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs index 849cd6c81a..321e710d8e 100644 --- a/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs +++ b/src/Umbraco.Web/Cache/DistributedCacheExtensions.cs @@ -1,465 +1,465 @@ -using System; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Events; -using Umbraco.Core.Models; -using umbraco; -using umbraco.cms.businesslogic.web; -using Umbraco.Core.Persistence.Repositories; - -namespace Umbraco.Web.Cache -{ - /// - /// Extension methods for - /// - internal static class DistributedCacheExtensions - { - #region Public access - - public static void RefreshPublicAccess(this DistributedCache dc) - { - dc.RefreshAll(DistributedCache.PublicAccessCacheRefresherGuid); - } - - #endregion - - #region Application tree cache - - public static void RefreshAllApplicationTreeCache(this DistributedCache dc) - { - dc.RefreshAll(DistributedCache.ApplicationTreeCacheRefresherGuid); - } - - #endregion - - #region Application cache - - public static void RefreshAllApplicationCache(this DistributedCache dc) - { - dc.RefreshAll(DistributedCache.ApplicationCacheRefresherGuid); - } - - #endregion - - #region User cache - - public static void RemoveUserCache(this DistributedCache dc, int userId) - { - dc.Remove(DistributedCache.UserCacheRefresherGuid, userId); - } - - public static void RefreshUserCache(this DistributedCache dc, int userId) - { - dc.Refresh(DistributedCache.UserCacheRefresherGuid, userId); - } - - public static void RefreshAllUserCache(this DistributedCache dc) - { - dc.RefreshAll(DistributedCache.UserCacheRefresherGuid); - } - - #endregion - - #region User group cache - - public static void RemoveUserGroupCache(this DistributedCache dc, int userId) - { - dc.Remove(DistributedCache.UserGroupCacheRefresherGuid, userId); - } - - public static void RefreshUserGroupCache(this DistributedCache dc, int userId) - { - dc.Refresh(DistributedCache.UserGroupCacheRefresherGuid, userId); - } - - public static void RefreshAllUserGroupCache(this DistributedCache dc) - { - dc.RefreshAll(DistributedCache.UserGroupCacheRefresherGuid); - } - - #endregion - - #region User group permissions cache - - public static void RemoveUserGroupPermissionsCache(this DistributedCache dc, int groupId) - { - dc.Remove(DistributedCache.UserGroupPermissionsCacheRefresherGuid, groupId); - } - - public static void RefreshUserGroupPermissionsCache(this DistributedCache dc, int groupId) - { - //TODO: Not sure if we need this yet depends if we start caching permissions - //dc.Refresh(DistributedCache.UserGroupPermissionsCacheRefresherGuid, groupId); - } - - public static void RefreshAllUserGroupPermissionsCache(this DistributedCache dc) - { - dc.RefreshAll(DistributedCache.UserGroupPermissionsCacheRefresherGuid); - } - - #endregion - - #region Template cache - - public static void RefreshTemplateCache(this DistributedCache dc, int templateId) - { - dc.Refresh(DistributedCache.TemplateRefresherGuid, templateId); - } - - public static void RemoveTemplateCache(this DistributedCache dc, int templateId) - { - dc.Remove(DistributedCache.TemplateRefresherGuid, templateId); - } - - #endregion - - #region Dictionary cache - - public static void RefreshDictionaryCache(this DistributedCache dc, int dictionaryItemId) - { - dc.Refresh(DistributedCache.DictionaryCacheRefresherGuid, dictionaryItemId); - } - - public static void RemoveDictionaryCache(this DistributedCache dc, int dictionaryItemId) - { - dc.Remove(DistributedCache.DictionaryCacheRefresherGuid, dictionaryItemId); - } - - #endregion - - #region Data type cache - - public static void RefreshDataTypeCache(this DistributedCache dc, IDataTypeDefinition dataType) - { - if (dataType == null) return; - dc.RefreshByJson(DistributedCache.DataTypeCacheRefresherGuid, DataTypeCacheRefresher.SerializeToJsonPayload(dataType)); - } - - public static void RemoveDataTypeCache(this DistributedCache dc, IDataTypeDefinition dataType) - { - if (dataType == null) return; - dc.RefreshByJson(DistributedCache.DataTypeCacheRefresherGuid, DataTypeCacheRefresher.SerializeToJsonPayload(dataType)); - } - - #endregion - - #region Page cache - - public static void RefreshAllPageCache(this DistributedCache dc) - { - dc.RefreshAll(DistributedCache.PageCacheRefresherGuid); - } - - public static void RefreshPageCache(this DistributedCache dc, int documentId) - { - dc.Refresh(DistributedCache.PageCacheRefresherGuid, documentId); - } - - public static void RefreshPageCache(this DistributedCache dc, params IContent[] content) - { - dc.Refresh(DistributedCache.PageCacheRefresherGuid, x => x.Id, content); - } - - public static void RemovePageCache(this DistributedCache dc, params IContent[] content) - { - dc.Remove(DistributedCache.PageCacheRefresherGuid, x => x.Id, content); - } - - public static void RemovePageCache(this DistributedCache dc, int documentId) - { - dc.Remove(DistributedCache.PageCacheRefresherGuid, documentId); - } - - public static void RefreshUnpublishedPageCache(this DistributedCache dc, params IContent[] content) - { - dc.Refresh(DistributedCache.UnpublishedPageCacheRefresherGuid, x => x.Id, content); - } - - public static void RemoveUnpublishedPageCache(this DistributedCache dc, params IContent[] content) - { - dc.Remove(DistributedCache.UnpublishedPageCacheRefresherGuid, x => x.Id, content); - } - - public static void RemoveUnpublishedCachePermanently(this DistributedCache dc, params int[] contentIds) - { - dc.RefreshByJson(DistributedCache.UnpublishedPageCacheRefresherGuid, UnpublishedPageCacheRefresher.SerializeToJsonPayloadForPermanentDeletion(contentIds)); - } - - #endregion - - #region Member cache - - public static void RefreshMemberCache(this DistributedCache dc, params IMember[] members) - { - dc.Refresh(DistributedCache.MemberCacheRefresherGuid, x => x.Id, members); - } - - public static void RemoveMemberCache(this DistributedCache dc, params IMember[] members) - { - dc.Remove(DistributedCache.MemberCacheRefresherGuid, x => x.Id, members); - } - - [Obsolete("Use the RefreshMemberCache with strongly typed IMember objects instead")] - public static void RefreshMemberCache(this DistributedCache dc, int memberId) - { - dc.Refresh(DistributedCache.MemberCacheRefresherGuid, memberId); - } - - [Obsolete("Use the RemoveMemberCache with strongly typed IMember objects instead")] - public static void RemoveMemberCache(this DistributedCache dc, int memberId) - { - dc.Remove(DistributedCache.MemberCacheRefresherGuid, memberId); - } - - #endregion - - #region Member group cache - - public static void RefreshMemberGroupCache(this DistributedCache dc, int memberGroupId) - { - dc.Refresh(DistributedCache.MemberGroupCacheRefresherGuid, memberGroupId); - } - - public static void RemoveMemberGroupCache(this DistributedCache dc, int memberGroupId) - { - dc.Remove(DistributedCache.MemberGroupCacheRefresherGuid, memberGroupId); - } - - #endregion - - #region Media Cache - - public static void RefreshMediaCache(this DistributedCache dc, params IMedia[] media) - { - dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayload(MediaCacheRefresher.OperationType.Saved, media)); - } - - public static void RefreshMediaCacheAfterMoving(this DistributedCache dc, params MoveEventInfo[] media) - { - dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayloadForMoving(MediaCacheRefresher.OperationType.Saved, media)); - } - - // clearing by Id will never work for load balanced scenarios for media since we require a Path - // to clear all of the cache but the media item will be removed before the other servers can - // look it up. Only here for legacy purposes. - [Obsolete("Ensure to clear with other RemoveMediaCache overload")] - public static void RemoveMediaCache(this DistributedCache dc, int mediaId) - { - dc.Remove(new Guid(DistributedCache.MediaCacheRefresherId), mediaId); - } - - public static void RemoveMediaCacheAfterRecycling(this DistributedCache dc, params MoveEventInfo[] media) - { - dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayloadForMoving(MediaCacheRefresher.OperationType.Trashed, media)); - } - - public static void RemoveMediaCachePermanently(this DistributedCache dc, params int[] mediaIds) - { - dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayloadForPermanentDeletion(mediaIds)); - } - - #endregion - - #region Macro Cache - - public static void ClearAllMacroCacheOnCurrentServer(this DistributedCache dc) - { - var macroRefresher = CacheRefreshersResolver.Current.GetById(DistributedCache.MacroCacheRefresherGuid); - macroRefresher.RefreshAll(); - } - - public static void RefreshMacroCache(this DistributedCache dc, IMacro macro) - { - if (macro == null) return; - dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); - } - - public static void RemoveMacroCache(this DistributedCache dc, IMacro macro) - { - if (macro == null) return; - dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); - } - - public static void RefreshMacroCache(this DistributedCache dc, global::umbraco.cms.businesslogic.macro.Macro macro) - { - if (macro == null) return; - dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); - } - - public static void RemoveMacroCache(this DistributedCache dc, global::umbraco.cms.businesslogic.macro.Macro macro) - { - if (macro == null) return; - dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); - } - - public static void RemoveMacroCache(this DistributedCache dc, macro macro) - { - if (macro == null || macro.Model == null) return; - dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); - } - - #endregion - - #region Document type cache - - public static void RefreshContentTypeCache(this DistributedCache dc, IContentType contentType) - { - if (contentType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(false, contentType)); - } - - public static void RemoveContentTypeCache(this DistributedCache dc, IContentType contentType) - { - if (contentType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(true, contentType)); - } - - #endregion - - #region Media type cache - - public static void RefreshMediaTypeCache(this DistributedCache dc, IMediaType mediaType) - { - if (mediaType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(false, mediaType)); - } - - public static void RemoveMediaTypeCache(this DistributedCache dc, IMediaType mediaType) - { - if (mediaType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(true, mediaType)); - } - - #endregion - - #region Media type cache - - public static void RefreshMemberTypeCache(this DistributedCache dc, IMemberType memberType) - { - if (memberType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(false, memberType)); - } - - public static void RemoveMemberTypeCache(this DistributedCache dc, IMemberType memberType) - { - if (memberType == null) return; - dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(true, memberType)); - } - - #endregion - - #region Stylesheet Cache - - public static void RefreshStylesheetPropertyCache(this DistributedCache dc, global::umbraco.cms.businesslogic.web.StylesheetProperty styleSheetProperty) - { - if (styleSheetProperty == null) return; - dc.Refresh(DistributedCache.StylesheetPropertyCacheRefresherGuid, styleSheetProperty.Id); - } - - public static void RemoveStylesheetPropertyCache(this DistributedCache dc, global::umbraco.cms.businesslogic.web.StylesheetProperty styleSheetProperty) - { - if (styleSheetProperty == null) return; - dc.Remove(DistributedCache.StylesheetPropertyCacheRefresherGuid, styleSheetProperty.Id); - } - - public static void RefreshStylesheetCache(this DistributedCache dc, StyleSheet styleSheet) - { - if (styleSheet == null) return; - dc.Refresh(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); - } - - public static void RemoveStylesheetCache(this DistributedCache dc, StyleSheet styleSheet) - { - if (styleSheet == null) return; - dc.Remove(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); - } - - public static void RefreshStylesheetCache(this DistributedCache dc, Umbraco.Core.Models.Stylesheet styleSheet) - { - if (styleSheet == null) return; - dc.Refresh(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); - } - - public static void RemoveStylesheetCache(this DistributedCache dc, Umbraco.Core.Models.Stylesheet styleSheet) - { - if (styleSheet == null) return; - dc.Remove(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); - } - - #endregion - - #region Domain Cache - - public static void RefreshDomainCache(this DistributedCache dc, IDomain domain) - { - if (domain == null) return; - dc.Refresh(DistributedCache.DomainCacheRefresherGuid, domain.Id); - } - - public static void RemoveDomainCache(this DistributedCache dc, IDomain domain) - { - if (domain == null) return; - dc.Remove(DistributedCache.DomainCacheRefresherGuid, domain.Id); - } - - public static void ClearDomainCacheOnCurrentServer(this DistributedCache dc) - { - var domainRefresher = CacheRefreshersResolver.Current.GetById(DistributedCache.DomainCacheRefresherGuid); - domainRefresher.RefreshAll(); - } - - #endregion - - #region Language Cache - - public static void RefreshLanguageCache(this DistributedCache dc, ILanguage language) - { - if (language == null) return; - dc.Refresh(DistributedCache.LanguageCacheRefresherGuid, language.Id); - } - - public static void RemoveLanguageCache(this DistributedCache dc, ILanguage language) - { - if (language == null) return; - dc.Remove(DistributedCache.LanguageCacheRefresherGuid, language.Id); - } - - public static void RefreshLanguageCache(this DistributedCache dc, global::umbraco.cms.businesslogic.language.Language language) - { - if (language == null) return; - dc.Refresh(DistributedCache.LanguageCacheRefresherGuid, language.id); - } - - public static void RemoveLanguageCache(this DistributedCache dc, global::umbraco.cms.businesslogic.language.Language language) - { - if (language == null) return; - dc.Remove(DistributedCache.LanguageCacheRefresherGuid, language.id); - } - - #endregion - - #region Xslt Cache - - public static void ClearXsltCacheOnCurrentServer(this DistributedCache dc) - { - if (UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration <= 0) return; - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes("MS.Internal.Xml.XPath.XPathSelectionIterator"); - } - - #endregion - - #region Relation type cache - - public static void RefreshRelationTypeCache(this DistributedCache dc, int id) - { - dc.Refresh(DistributedCache.RelationTypeCacheRefresherGuid, id); - } - - public static void RemoveRelationTypeCache(this DistributedCache dc, int id) - { - dc.Remove(DistributedCache.RelationTypeCacheRefresherGuid, id); - } - - #endregion - } -} \ No newline at end of file +using System; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Events; +using Umbraco.Core.Models; +using umbraco; +using umbraco.cms.businesslogic.web; +using Umbraco.Core.Persistence.Repositories; + +namespace Umbraco.Web.Cache +{ + /// + /// Extension methods for + /// + internal static class DistributedCacheExtensions + { + #region Public access + + public static void RefreshPublicAccess(this DistributedCache dc) + { + dc.RefreshAll(DistributedCache.PublicAccessCacheRefresherGuid); + } + + #endregion + + #region Application tree cache + + public static void RefreshAllApplicationTreeCache(this DistributedCache dc) + { + dc.RefreshAll(DistributedCache.ApplicationTreeCacheRefresherGuid); + } + + #endregion + + #region Application cache + + public static void RefreshAllApplicationCache(this DistributedCache dc) + { + dc.RefreshAll(DistributedCache.ApplicationCacheRefresherGuid); + } + + #endregion + + #region User cache + + public static void RemoveUserCache(this DistributedCache dc, int userId) + { + dc.Remove(DistributedCache.UserCacheRefresherGuid, userId); + } + + public static void RefreshUserCache(this DistributedCache dc, int userId) + { + dc.Refresh(DistributedCache.UserCacheRefresherGuid, userId); + } + + public static void RefreshAllUserCache(this DistributedCache dc) + { + dc.RefreshAll(DistributedCache.UserCacheRefresherGuid); + } + + #endregion + + #region User group cache + + public static void RemoveUserGroupCache(this DistributedCache dc, int userId) + { + dc.Remove(DistributedCache.UserGroupCacheRefresherGuid, userId); + } + + public static void RefreshUserGroupCache(this DistributedCache dc, int userId) + { + dc.Refresh(DistributedCache.UserGroupCacheRefresherGuid, userId); + } + + public static void RefreshAllUserGroupCache(this DistributedCache dc) + { + dc.RefreshAll(DistributedCache.UserGroupCacheRefresherGuid); + } + + #endregion + + #region User group permissions cache + + public static void RemoveUserGroupPermissionsCache(this DistributedCache dc, int groupId) + { + dc.Remove(DistributedCache.UserGroupPermissionsCacheRefresherGuid, groupId); + } + + public static void RefreshUserGroupPermissionsCache(this DistributedCache dc, int groupId) + { + //TODO: Not sure if we need this yet depends if we start caching permissions + //dc.Refresh(DistributedCache.UserGroupPermissionsCacheRefresherGuid, groupId); + } + + public static void RefreshAllUserGroupPermissionsCache(this DistributedCache dc) + { + dc.RefreshAll(DistributedCache.UserGroupPermissionsCacheRefresherGuid); + } + + #endregion + + #region Template cache + + public static void RefreshTemplateCache(this DistributedCache dc, int templateId) + { + dc.Refresh(DistributedCache.TemplateRefresherGuid, templateId); + } + + public static void RemoveTemplateCache(this DistributedCache dc, int templateId) + { + dc.Remove(DistributedCache.TemplateRefresherGuid, templateId); + } + + #endregion + + #region Dictionary cache + + public static void RefreshDictionaryCache(this DistributedCache dc, int dictionaryItemId) + { + dc.Refresh(DistributedCache.DictionaryCacheRefresherGuid, dictionaryItemId); + } + + public static void RemoveDictionaryCache(this DistributedCache dc, int dictionaryItemId) + { + dc.Remove(DistributedCache.DictionaryCacheRefresherGuid, dictionaryItemId); + } + + #endregion + + #region Data type cache + + public static void RefreshDataTypeCache(this DistributedCache dc, IDataTypeDefinition dataType) + { + if (dataType == null) return; + dc.RefreshByJson(DistributedCache.DataTypeCacheRefresherGuid, DataTypeCacheRefresher.SerializeToJsonPayload(dataType)); + } + + public static void RemoveDataTypeCache(this DistributedCache dc, IDataTypeDefinition dataType) + { + if (dataType == null) return; + dc.RefreshByJson(DistributedCache.DataTypeCacheRefresherGuid, DataTypeCacheRefresher.SerializeToJsonPayload(dataType)); + } + + #endregion + + #region Page cache + + public static void RefreshAllPageCache(this DistributedCache dc) + { + dc.RefreshAll(DistributedCache.PageCacheRefresherGuid); + } + + public static void RefreshPageCache(this DistributedCache dc, int documentId) + { + dc.Refresh(DistributedCache.PageCacheRefresherGuid, documentId); + } + + public static void RefreshPageCache(this DistributedCache dc, params IContent[] content) + { + dc.Refresh(DistributedCache.PageCacheRefresherGuid, x => x.Id, content); + } + + public static void RemovePageCache(this DistributedCache dc, params IContent[] content) + { + dc.Remove(DistributedCache.PageCacheRefresherGuid, x => x.Id, content); + } + + public static void RemovePageCache(this DistributedCache dc, int documentId) + { + dc.Remove(DistributedCache.PageCacheRefresherGuid, documentId); + } + + public static void RefreshUnpublishedPageCache(this DistributedCache dc, params IContent[] content) + { + dc.RefreshByJson(DistributedCache.UnpublishedPageCacheRefresherGuid, UnpublishedPageCacheRefresher.SerializeToJsonPayload(UnpublishedPageCacheRefresher.OperationType.Refresh, content)); + } + + public static void RemoveUnpublishedPageCache(this DistributedCache dc, params IContent[] content) + { + dc.RefreshByJson(DistributedCache.UnpublishedPageCacheRefresherGuid, UnpublishedPageCacheRefresher.SerializeToJsonPayload(UnpublishedPageCacheRefresher.OperationType.Deleted, content)); + } + + public static void RemoveUnpublishedCachePermanently(this DistributedCache dc, params int[] contentIds) + { + dc.RefreshByJson(DistributedCache.UnpublishedPageCacheRefresherGuid, UnpublishedPageCacheRefresher.SerializeToJsonPayloadForPermanentDeletion(contentIds)); + } + + #endregion + + #region Member cache + + public static void RefreshMemberCache(this DistributedCache dc, params IMember[] members) + { + dc.Refresh(DistributedCache.MemberCacheRefresherGuid, x => x.Id, members); + } + + public static void RemoveMemberCache(this DistributedCache dc, params IMember[] members) + { + dc.Remove(DistributedCache.MemberCacheRefresherGuid, x => x.Id, members); + } + + [Obsolete("Use the RefreshMemberCache with strongly typed IMember objects instead")] + public static void RefreshMemberCache(this DistributedCache dc, int memberId) + { + dc.Refresh(DistributedCache.MemberCacheRefresherGuid, memberId); + } + + [Obsolete("Use the RemoveMemberCache with strongly typed IMember objects instead")] + public static void RemoveMemberCache(this DistributedCache dc, int memberId) + { + dc.Remove(DistributedCache.MemberCacheRefresherGuid, memberId); + } + + #endregion + + #region Member group cache + + public static void RefreshMemberGroupCache(this DistributedCache dc, int memberGroupId) + { + dc.Refresh(DistributedCache.MemberGroupCacheRefresherGuid, memberGroupId); + } + + public static void RemoveMemberGroupCache(this DistributedCache dc, int memberGroupId) + { + dc.Remove(DistributedCache.MemberGroupCacheRefresherGuid, memberGroupId); + } + + #endregion + + #region Media Cache + + public static void RefreshMediaCache(this DistributedCache dc, params IMedia[] media) + { + dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayload(MediaCacheRefresher.OperationType.Saved, media)); + } + + public static void RefreshMediaCacheAfterMoving(this DistributedCache dc, params MoveEventInfo[] media) + { + dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayloadForMoving(MediaCacheRefresher.OperationType.Saved, media)); + } + + // clearing by Id will never work for load balanced scenarios for media since we require a Path + // to clear all of the cache but the media item will be removed before the other servers can + // look it up. Only here for legacy purposes. + [Obsolete("Ensure to clear with other RemoveMediaCache overload")] + public static void RemoveMediaCache(this DistributedCache dc, int mediaId) + { + dc.Remove(new Guid(DistributedCache.MediaCacheRefresherId), mediaId); + } + + public static void RemoveMediaCacheAfterRecycling(this DistributedCache dc, params MoveEventInfo[] media) + { + dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayloadForMoving(MediaCacheRefresher.OperationType.Trashed, media)); + } + + public static void RemoveMediaCachePermanently(this DistributedCache dc, params int[] mediaIds) + { + dc.RefreshByJson(DistributedCache.MediaCacheRefresherGuid, MediaCacheRefresher.SerializeToJsonPayloadForPermanentDeletion(mediaIds)); + } + + #endregion + + #region Macro Cache + + public static void ClearAllMacroCacheOnCurrentServer(this DistributedCache dc) + { + var macroRefresher = CacheRefreshersResolver.Current.GetById(DistributedCache.MacroCacheRefresherGuid); + macroRefresher.RefreshAll(); + } + + public static void RefreshMacroCache(this DistributedCache dc, IMacro macro) + { + if (macro == null) return; + dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); + } + + public static void RemoveMacroCache(this DistributedCache dc, IMacro macro) + { + if (macro == null) return; + dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); + } + + public static void RefreshMacroCache(this DistributedCache dc, global::umbraco.cms.businesslogic.macro.Macro macro) + { + if (macro == null) return; + dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); + } + + public static void RemoveMacroCache(this DistributedCache dc, global::umbraco.cms.businesslogic.macro.Macro macro) + { + if (macro == null) return; + dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); + } + + public static void RemoveMacroCache(this DistributedCache dc, macro macro) + { + if (macro == null || macro.Model == null) return; + dc.RefreshByJson(DistributedCache.MacroCacheRefresherGuid, MacroCacheRefresher.SerializeToJsonPayload(macro)); + } + + #endregion + + #region Document type cache + + public static void RefreshContentTypeCache(this DistributedCache dc, IContentType contentType) + { + if (contentType == null) return; + dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(false, contentType)); + } + + public static void RemoveContentTypeCache(this DistributedCache dc, IContentType contentType) + { + if (contentType == null) return; + dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(true, contentType)); + } + + #endregion + + #region Media type cache + + public static void RefreshMediaTypeCache(this DistributedCache dc, IMediaType mediaType) + { + if (mediaType == null) return; + dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(false, mediaType)); + } + + public static void RemoveMediaTypeCache(this DistributedCache dc, IMediaType mediaType) + { + if (mediaType == null) return; + dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(true, mediaType)); + } + + #endregion + + #region Media type cache + + public static void RefreshMemberTypeCache(this DistributedCache dc, IMemberType memberType) + { + if (memberType == null) return; + dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(false, memberType)); + } + + public static void RemoveMemberTypeCache(this DistributedCache dc, IMemberType memberType) + { + if (memberType == null) return; + dc.RefreshByJson(DistributedCache.ContentTypeCacheRefresherGuid, ContentTypeCacheRefresher.SerializeToJsonPayload(true, memberType)); + } + + #endregion + + #region Stylesheet Cache + + public static void RefreshStylesheetPropertyCache(this DistributedCache dc, global::umbraco.cms.businesslogic.web.StylesheetProperty styleSheetProperty) + { + if (styleSheetProperty == null) return; + dc.Refresh(DistributedCache.StylesheetPropertyCacheRefresherGuid, styleSheetProperty.Id); + } + + public static void RemoveStylesheetPropertyCache(this DistributedCache dc, global::umbraco.cms.businesslogic.web.StylesheetProperty styleSheetProperty) + { + if (styleSheetProperty == null) return; + dc.Remove(DistributedCache.StylesheetPropertyCacheRefresherGuid, styleSheetProperty.Id); + } + + public static void RefreshStylesheetCache(this DistributedCache dc, StyleSheet styleSheet) + { + if (styleSheet == null) return; + dc.Refresh(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); + } + + public static void RemoveStylesheetCache(this DistributedCache dc, StyleSheet styleSheet) + { + if (styleSheet == null) return; + dc.Remove(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); + } + + public static void RefreshStylesheetCache(this DistributedCache dc, Umbraco.Core.Models.Stylesheet styleSheet) + { + if (styleSheet == null) return; + dc.Refresh(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); + } + + public static void RemoveStylesheetCache(this DistributedCache dc, Umbraco.Core.Models.Stylesheet styleSheet) + { + if (styleSheet == null) return; + dc.Remove(DistributedCache.StylesheetCacheRefresherGuid, styleSheet.Id); + } + + #endregion + + #region Domain Cache + + public static void RefreshDomainCache(this DistributedCache dc, IDomain domain) + { + if (domain == null) return; + dc.Refresh(DistributedCache.DomainCacheRefresherGuid, domain.Id); + } + + public static void RemoveDomainCache(this DistributedCache dc, IDomain domain) + { + if (domain == null) return; + dc.Remove(DistributedCache.DomainCacheRefresherGuid, domain.Id); + } + + public static void ClearDomainCacheOnCurrentServer(this DistributedCache dc) + { + var domainRefresher = CacheRefreshersResolver.Current.GetById(DistributedCache.DomainCacheRefresherGuid); + domainRefresher.RefreshAll(); + } + + #endregion + + #region Language Cache + + public static void RefreshLanguageCache(this DistributedCache dc, ILanguage language) + { + if (language == null) return; + dc.Refresh(DistributedCache.LanguageCacheRefresherGuid, language.Id); + } + + public static void RemoveLanguageCache(this DistributedCache dc, ILanguage language) + { + if (language == null) return; + dc.Remove(DistributedCache.LanguageCacheRefresherGuid, language.Id); + } + + public static void RefreshLanguageCache(this DistributedCache dc, global::umbraco.cms.businesslogic.language.Language language) + { + if (language == null) return; + dc.Refresh(DistributedCache.LanguageCacheRefresherGuid, language.id); + } + + public static void RemoveLanguageCache(this DistributedCache dc, global::umbraco.cms.businesslogic.language.Language language) + { + if (language == null) return; + dc.Remove(DistributedCache.LanguageCacheRefresherGuid, language.id); + } + + #endregion + + #region Xslt Cache + + public static void ClearXsltCacheOnCurrentServer(this DistributedCache dc) + { + if (UmbracoConfig.For.UmbracoSettings().Content.UmbracoLibraryCacheDuration <= 0) return; + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheObjectTypes("MS.Internal.Xml.XPath.XPathSelectionIterator"); + } + + #endregion + + #region Relation type cache + + public static void RefreshRelationTypeCache(this DistributedCache dc, int id) + { + dc.Refresh(DistributedCache.RelationTypeCacheRefresherGuid, id); + } + + public static void RemoveRelationTypeCache(this DistributedCache dc, int id) + { + dc.Remove(DistributedCache.RelationTypeCacheRefresherGuid, id); + } + + #endregion + } +} diff --git a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs index 783ca95841..bdaf9aa9e3 100644 --- a/src/Umbraco.Web/Cache/MediaCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MediaCacheRefresher.cs @@ -1,194 +1,196 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Web.Script.Serialization; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Events; -using Umbraco.Core.IO; -using Umbraco.Core.Models; - -using Umbraco.Core.Persistence.Repositories; -using umbraco.interfaces; -using System.Linq; -using Newtonsoft.Json; -using Umbraco.Web.PublishedCache.XmlPublishedCache; - -namespace Umbraco.Web.Cache -{ - /// - /// A cache refresher to ensure media cache is updated - /// - /// - /// This is not intended to be used directly in your code and it should be sealed but due to legacy code we cannot seal it. - /// - public class MediaCacheRefresher : JsonCacheRefresherBase - { - #region Static helpers - - /// - /// Converts the json to a JsonPayload object - /// - /// - /// - public static JsonPayload[] DeserializeFromJsonPayload(string json) - { - var jsonObject = JsonConvert.DeserializeObject(json); - return jsonObject; - } - - /// - /// Creates the custom Json payload used to refresh cache amongst the servers - /// - /// - /// - /// - internal static string SerializeToJsonPayload(OperationType operation, params IMedia[] media) - { - var items = media.Select(x => FromMedia(x, operation)).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } - - internal static string SerializeToJsonPayloadForMoving(OperationType operation, MoveEventInfo[] media) - { - var items = media.Select(x => new JsonPayload - { - Id = x.Entity.Id, - Operation = operation, - Path = x.OriginalPath - }).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } - - internal static string SerializeToJsonPayloadForPermanentDeletion(params int[] mediaIds) - { - var items = mediaIds.Select(x => new JsonPayload - { - Id = x, - Operation = OperationType.Deleted - }).ToArray(); - var json = JsonConvert.SerializeObject(items); - return json; - } - - /// - /// Converts a macro to a jsonPayload object - /// - /// - /// - /// - internal static JsonPayload FromMedia(IMedia media, OperationType operation) - { - if (media == null) return null; - - var payload = new JsonPayload - { - Id = media.Id, - Path = media.Path, - Operation = operation - }; - return payload; - } - - #endregion - - #region Sub classes - - public enum OperationType - { - Saved, - Trashed, - Deleted - } - - public class JsonPayload - { - public string Path { get; set; } - public int Id { get; set; } - public OperationType Operation { get; set; } - } - - #endregion - - protected override MediaCacheRefresher Instance - { - get { return this; } - } - - public override Guid UniqueIdentifier - { - get { return new Guid(DistributedCache.MediaCacheRefresherId); } - } - - public override string Name - { - get { return "Clears Media Cache from umbraco.library"; } - } - - public override void Refresh(string jsonPayload) - { - ClearCache(DeserializeFromJsonPayload(jsonPayload)); - base.Refresh(jsonPayload); - } - - public override void Refresh(int id) - { - ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id), OperationType.Saved)); - base.Refresh(id); - } - - public override void Remove(int id) - { - ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id), - //NOTE: we'll just default to trashed for this one. - OperationType.Trashed)); - base.Remove(id); - } - - private static void ClearCache(params JsonPayload[] payloads) - { - if (payloads == null) return; - - ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); - - foreach (var payload in payloads) - { - if (payload.Operation == OperationType.Deleted) - ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id); - - var mediaCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); - - //if there's no path, then just use id (this will occur on permanent deletion like emptying recycle bin) - if (payload.Path.IsNullOrWhiteSpace()) - { - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); - } - else - { - foreach (var idPart in payload.Path.Split(',')) - { - int idPartAsInt; - if (int.TryParse(idPart, out idPartAsInt) && mediaCache) - { - mediaCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(idPartAsInt)); - } - - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}_{1}_True", CacheKeys.MediaCacheKey, idPart)); - - // Also clear calls that only query this specific item! - if (idPart == payload.Id.ToString(CultureInfo.InvariantCulture)) - ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( - string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); - } - } - - // published cache... - PublishedMediaCache.ClearCache(payload.Id); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Web.Script.Serialization; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Events; +using Umbraco.Core.IO; +using Umbraco.Core.Models; + +using Umbraco.Core.Persistence.Repositories; +using umbraco.interfaces; +using System.Linq; +using Newtonsoft.Json; +using Umbraco.Web.PublishedCache.XmlPublishedCache; + +namespace Umbraco.Web.Cache +{ + /// + /// A cache refresher to ensure media cache is updated + /// + /// + /// This is not intended to be used directly in your code and it should be sealed but due to legacy code we cannot seal it. + /// + public class MediaCacheRefresher : JsonCacheRefresherBase + { + #region Static helpers + + /// + /// Converts the json to a JsonPayload object + /// + /// + /// + public static JsonPayload[] DeserializeFromJsonPayload(string json) + { + var jsonObject = JsonConvert.DeserializeObject(json); + return jsonObject; + } + + /// + /// Creates the custom Json payload used to refresh cache amongst the servers + /// + /// + /// + /// + internal static string SerializeToJsonPayload(OperationType operation, params IMedia[] media) + { + var items = media.Select(x => FromMedia(x, operation)).ToArray(); + var json = JsonConvert.SerializeObject(items); + return json; + } + + internal static string SerializeToJsonPayloadForMoving(OperationType operation, MoveEventInfo[] media) + { + var items = media.Select(x => new JsonPayload + { + Id = x.Entity.Id, + Operation = operation, + Path = x.OriginalPath + }).ToArray(); + var json = JsonConvert.SerializeObject(items); + return json; + } + + internal static string SerializeToJsonPayloadForPermanentDeletion(params int[] mediaIds) + { + var items = mediaIds.Select(x => new JsonPayload + { + Id = x, + Operation = OperationType.Deleted + }).ToArray(); + var json = JsonConvert.SerializeObject(items); + return json; + } + + /// + /// Converts a macro to a jsonPayload object + /// + /// + /// + /// + internal static JsonPayload FromMedia(IMedia media, OperationType operation) + { + if (media == null) return null; + + var payload = new JsonPayload + { + Id = media.Id, + Key = media.Key, + Path = media.Path, + Operation = operation + }; + return payload; + } + + #endregion + + #region Sub classes + + public enum OperationType + { + Saved, + Trashed, + Deleted + } + + public class JsonPayload + { + public string Path { get; set; } + public int Id { get; set; } + public OperationType Operation { get; set; } + public Guid? Key { get; set; } + } + + #endregion + + protected override MediaCacheRefresher Instance + { + get { return this; } + } + + public override Guid UniqueIdentifier + { + get { return new Guid(DistributedCache.MediaCacheRefresherId); } + } + + public override string Name + { + get { return "Clears Media Cache from umbraco.library"; } + } + + public override void Refresh(string jsonPayload) + { + ClearCache(DeserializeFromJsonPayload(jsonPayload)); + base.Refresh(jsonPayload); + } + + public override void Refresh(int id) + { + ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id), OperationType.Saved)); + base.Refresh(id); + } + + public override void Remove(int id) + { + ClearCache(FromMedia(ApplicationContext.Current.Services.MediaService.GetById(id), + //NOTE: we'll just default to trashed for this one. + OperationType.Trashed)); + base.Remove(id); + } + + private static void ClearCache(params JsonPayload[] payloads) + { + if (payloads == null) return; + + ApplicationContext.Current.ApplicationCache.ClearPartialViewCache(); + + foreach (var payload in payloads) + { + if (payload.Operation == OperationType.Deleted) + ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id); + + var mediaCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + mediaCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(payload.Key)); + //if there's no path, then just use id (this will occur on permanent deletion like emptying recycle bin) + if (payload.Path.IsNullOrWhiteSpace()) + { + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); + } + else + { + foreach (var idPart in payload.Path.Split(',')) + { + int idPartAsInt; + if (int.TryParse(idPart, out idPartAsInt) && mediaCache) + { + mediaCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(idPartAsInt)); + } + + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + string.Format("{0}_{1}_True", CacheKeys.MediaCacheKey, idPart)); + + // Also clear calls that only query this specific item! + if (idPart == payload.Id.ToString(CultureInfo.InvariantCulture)) + ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch( + string.Format("{0}_{1}", CacheKeys.MediaCacheKey, payload.Id)); + } + } + + // published cache... + PublishedMediaCache.ClearCache(payload.Id); + } + } + } +} diff --git a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs index bf58454bfc..4fc38a27a4 100644 --- a/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/UnpublishedPageCacheRefresher.cs @@ -56,6 +56,17 @@ namespace Umbraco.Web.Cache var json = JsonConvert.SerializeObject(items); return json; } + internal static string SerializeToJsonPayload(OperationType operationType, params IContent[] contents) + { + var items = contents.Select(x => new JsonPayload + { + Id = x.Id, + Key = x.Key, + Operation = operationType + }).ToArray(); + var json = JsonConvert.SerializeObject(items); + return json; + } #endregion @@ -63,12 +74,14 @@ namespace Umbraco.Web.Cache internal enum OperationType { - Deleted + Deleted, + Refresh } internal class JsonPayload { public int Id { get; set; } + public Guid? Key { get; set; } public OperationType Operation { get; set; } } @@ -139,10 +152,26 @@ namespace Umbraco.Web.Cache foreach (var payload in DeserializeFromJsonPayload(jsonPayload)) { - ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id); ClearRepositoryCacheItemById(payload.Id); - content.Instance.UpdateSortOrder(payload.Id); - content.Instance.ClearPreviewXmlContent(payload.Id); + ClearRepositoryCacheItemById(payload.Key); + ClearAllIsolatedCacheByEntityType(); + + if (payload.Operation == OperationType.Deleted) + { + ApplicationContext.Current.Services.IdkMap.ClearCache(payload.Id); + content.Instance.ClearPreviewXmlContent(payload.Id); + base.Remove(payload.Id); + } + + if (payload.Operation == OperationType.Refresh) + { + content.Instance.UpdateSortOrder(payload.Id); + var d = new Document(payload.Id); + content.Instance.UpdateDocumentCache(d); + content.Instance.UpdatePreviewXmlContent(d); + + base.Refresh(payload.Id); + } } DistributedCache.Instance.ClearDomainCacheOnCurrentServer(); @@ -158,5 +187,14 @@ namespace Umbraco.Web.Cache contentCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(id)); } } + + private void ClearRepositoryCacheItemById(Guid? key) + { + var contentCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); + if (contentCache) + { + contentCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(key)); + } + } } } diff --git a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs index 3bc0ecb7cd..14ee38031d 100644 --- a/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs +++ b/src/Umbraco.Web/PublishedCache/XmlPublishedCache/PublishedMediaCache.cs @@ -1,1047 +1,1047 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Configuration; -using System.IO; -using System.Linq; -using System.Threading; -using System.Xml.XPath; -using Examine; -using Examine.LuceneEngine.SearchCriteria; -using Examine.Providers; -using Lucene.Net.Documents; -using Lucene.Net.Store; -using Umbraco.Core; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dynamics; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Models.PublishedContent; -using Umbraco.Core.Xml; -using Umbraco.Web.Models; -using UmbracoExamine; -using umbraco; -using Umbraco.Core.Cache; -using Umbraco.Core.Sync; -using Umbraco.Web.Cache; - -namespace Umbraco.Web.PublishedCache.XmlPublishedCache -{ - /// - /// An IPublishedMediaStore that first checks for the media in Examine, and then reverts to the database - /// - /// - /// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly. - /// - internal class PublishedMediaCache : IPublishedMediaCache - { - public PublishedMediaCache(ApplicationContext applicationContext) - { - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); - _applicationContext = applicationContext; - } - - /// - /// Generally used for unit testing to use an explicit examine searcher - /// - /// - /// - /// - internal PublishedMediaCache(ApplicationContext applicationContext, BaseSearchProvider searchProvider, BaseIndexProvider indexProvider) - { - if (applicationContext == null) throw new ArgumentNullException("applicationContext"); - if (searchProvider == null) throw new ArgumentNullException("searchProvider"); - if (indexProvider == null) throw new ArgumentNullException("indexProvider"); - - _applicationContext = applicationContext; - _searchProvider = searchProvider; - _indexProvider = indexProvider; - } - - static PublishedMediaCache() - { - InitializeCacheConfig(); - } - - private readonly ApplicationContext _applicationContext; - private readonly BaseSearchProvider _searchProvider; - private readonly BaseIndexProvider _indexProvider; - - public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int nodeId) - { - return GetUmbracoMedia(nodeId); - } - - public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, Guid nodeKey) - { - // TODO optimize with Examine? - var mapAttempt = ApplicationContext.Current.Services.IdkMap.GetIdForKey(nodeKey, UmbracoObjectTypes.Media); - return mapAttempt ? GetById(umbracoContext, preview, mapAttempt.Result) : null; - } - - public virtual IEnumerable GetAtRoot(UmbracoContext umbracoContext, bool preview) - { - var searchProvider = GetSearchProviderSafe(); - - if (searchProvider != null) - { - try - { - // first check in Examine for the cache values - // +(+parentID:-1) +__IndexType:media - - var criteria = searchProvider.CreateSearchCriteria("media"); - var filter = criteria.ParentId(-1).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); - - var result = searchProvider.Search(filter.Compile()); - if (result != null) - return result.Select(x => CreateFromCacheValues(ConvertFromSearchResult(x))); - } - catch (Exception ex) - { - if (ex is FileNotFoundException) - { - //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco - //See this thread: http://examine.cdodeplex.com/discussions/264341 - //Catch the exception here for the time being, and just fallback to GetMedia - //TODO: Need to fix examine in LB scenarios! - LogHelper.Error("Could not load data from Examine index for media", ex); - } - else if (ex is AlreadyClosedException) - { - //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot - //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. - LogHelper.Error("Could not load data from Examine index for media, the app domain is most likely in a shutdown state", ex); - } - else throw; - } +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Configuration; +using System.IO; +using System.Linq; +using System.Threading; +using System.Xml.XPath; +using Examine; +using Examine.LuceneEngine.SearchCriteria; +using Examine.Providers; +using Lucene.Net.Documents; +using Lucene.Net.Store; +using Umbraco.Core; +using Umbraco.Core.Configuration; +using Umbraco.Core.Dynamics; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.Xml; +using Umbraco.Web.Models; +using UmbracoExamine; +using umbraco; +using Umbraco.Core.Cache; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; + +namespace Umbraco.Web.PublishedCache.XmlPublishedCache +{ + /// + /// An IPublishedMediaStore that first checks for the media in Examine, and then reverts to the database + /// + /// + /// NOTE: In the future if we want to properly cache all media this class can be extended or replaced when these classes/interfaces are exposed publicly. + /// + internal class PublishedMediaCache : IPublishedMediaCache + { + public PublishedMediaCache(ApplicationContext applicationContext) + { + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + _applicationContext = applicationContext; + } + + /// + /// Generally used for unit testing to use an explicit examine searcher + /// + /// + /// + /// + internal PublishedMediaCache(ApplicationContext applicationContext, BaseSearchProvider searchProvider, BaseIndexProvider indexProvider) + { + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + if (searchProvider == null) throw new ArgumentNullException("searchProvider"); + if (indexProvider == null) throw new ArgumentNullException("indexProvider"); + + _applicationContext = applicationContext; + _searchProvider = searchProvider; + _indexProvider = indexProvider; + } + + static PublishedMediaCache() + { + InitializeCacheConfig(); + } + + private readonly ApplicationContext _applicationContext; + private readonly BaseSearchProvider _searchProvider; + private readonly BaseIndexProvider _indexProvider; + + public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, int nodeId) + { + return GetUmbracoMedia(nodeId); + } + + public virtual IPublishedContent GetById(UmbracoContext umbracoContext, bool preview, Guid nodeKey) + { + // TODO optimize with Examine? + var mapAttempt = ApplicationContext.Current.Services.IdkMap.GetIdForKey(nodeKey, UmbracoObjectTypes.Media); + return mapAttempt ? GetById(umbracoContext, preview, mapAttempt.Result) : null; + } + + public virtual IEnumerable GetAtRoot(UmbracoContext umbracoContext, bool preview) + { + var searchProvider = GetSearchProviderSafe(); + + if (searchProvider != null) + { + try + { + // first check in Examine for the cache values + // +(+parentID:-1) +__IndexType:media + + var criteria = searchProvider.CreateSearchCriteria("media"); + var filter = criteria.ParentId(-1).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); + + var result = searchProvider.Search(filter.Compile()); + if (result != null) + return result.Select(x => CreateFromCacheValues(ConvertFromSearchResult(x))); + } + catch (Exception ex) + { + if (ex is FileNotFoundException) + { + //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco + //See this thread: http://examine.cdodeplex.com/discussions/264341 + //Catch the exception here for the time being, and just fallback to GetMedia + //TODO: Need to fix examine in LB scenarios! + LogHelper.Error("Could not load data from Examine index for media", ex); + } + else if (ex is AlreadyClosedException) + { + //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot + //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. + LogHelper.Error("Could not load data from Examine index for media, the app domain is most likely in a shutdown state", ex); + } + else throw; + } } //something went wrong, fetch from the db - - var rootMedia = _applicationContext.Services.MediaService.GetRootMedia(); - return rootMedia.Select(m => CreateFromCacheValues(ConvertFromIMedia(m))); + + var rootMedia = _applicationContext.Services.MediaService.GetRootMedia(); + return rootMedia.Select(m => CreateFromCacheValues(ConvertFromIMedia(m))); } - - public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, string xpath, XPathVariable[] vars) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - } - - public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, XPathVariable[] vars) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - } - - public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, XPathVariable[] vars) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - } - - public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, XPathVariable[] vars) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - } - - public virtual XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext, bool preview) - { - throw new NotImplementedException("PublishedMediaCache does not support XPath."); - } - - public bool XPathNavigatorIsNavigable { get { return false; } } - - public virtual bool HasContent(UmbracoContext context, bool preview) { throw new NotImplementedException(); } - - private ExamineManager GetExamineManagerSafe() - { - try - { - return ExamineManager.Instance; - } - catch (TypeInitializationException) - { - return null; - } - } - - private BaseIndexProvider GetIndexProviderSafe() - { - if (_indexProvider != null) - return _indexProvider; - - var eMgr = GetExamineManagerSafe(); - if (eMgr != null) - { - try - { - //by default use the InternalSearcher - var indexer = eMgr.IndexProviderCollection[Constants.Examine.InternalIndexer]; - if (indexer.IndexerData.IncludeNodeTypes.Any() || indexer.IndexerData.ExcludeNodeTypes.Any()) - { - LogHelper.Warn("The InternalIndexer for examine is configured incorrectly, it should not list any include/exclude node types or field names, it should simply be configured as: " + ""); - } - return indexer; - } - catch (Exception ex) - { - LogHelper.Error("Could not retrieve the InternalIndexer", ex); - //something didn't work, continue returning null. - } - } - return null; - } - - private BaseSearchProvider GetSearchProviderSafe() - { - if (_searchProvider != null) - return _searchProvider; - - var eMgr = GetExamineManagerSafe(); - if (eMgr != null) - { - try - { - //by default use the InternalSearcher - return eMgr.SearchProviderCollection[Constants.Examine.InternalSearcher]; - } - catch (FileNotFoundException) - { - //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco - //See this thread: http://examine.cdodeplex.com/discussions/264341 - //Catch the exception here for the time being, and just fallback to GetMedia - //TODO: Need to fix examine in LB scenarios! - } - catch (NullReferenceException) - { - //This will occur when the search provider cannot be initialized. In newer examine versions the initialization is lazy and therefore - // the manager will return the singleton without throwing initialization errors, however if examine isn't configured correctly a null - // reference error will occur because the examine settings are null. - } - catch (AlreadyClosedException) - { - //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot - //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. - } - } - return null; - } - - private IPublishedContent GetUmbracoMedia(int id) - { - // this recreates an IPublishedContent and model each time - // it is called, but at least it should NOT hit the database - // nor Lucene each time, relying on the memory cache instead - - if (id <= 0) return null; // fail fast - - var cacheValues = GetCacheValues(id, GetUmbracoMediaCacheValues); - - return cacheValues == null ? null : CreateFromCacheValues(cacheValues); - } - - private CacheValues GetUmbracoMediaCacheValues(int id) - { - var searchProvider = GetSearchProviderSafe(); - - if (searchProvider != null) - { - try - { - // first check in Examine as this is WAY faster - // - // the filter will create a query like this: - // +(+__NodeId:3113 -__Path:-1,-21,*) +__IndexType:media - // - // note that since the use of the wildcard, it automatically escapes it in Lucene. - - var criteria = searchProvider.CreateSearchCriteria("media"); - var filter = criteria.Id(id).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); - - var result = searchProvider.Search(filter.Compile()).FirstOrDefault(); - if (result != null) return ConvertFromSearchResult(result); - } - catch (Exception ex) - { - if (ex is FileNotFoundException) - { - //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco - //See this thread: http://examine.cdodeplex.com/discussions/264341 - //Catch the exception here for the time being, and just fallback to GetMedia - //TODO: Need to fix examine in LB scenarios! - LogHelper.Error("Could not load data from Examine index for media", ex); - } - else if (ex is AlreadyClosedException) - { - //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot - //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. - LogHelper.Error("Could not load data from Examine index for media, the app domain is most likely in a shutdown state", ex); - } - else throw; - } - } - - // don't log a warning here, as it can flood the log in case of eg a media picker referencing a media - // that has been deleted, hence is not in the Examine index anymore (for a good reason). try to get - // the media from the service, first - - var media = ApplicationContext.Current.Services.MediaService.GetById(id); - if (media == null || media.Trashed) return null; // not found, ok - - // so, the media was not found in Examine's index *yet* it exists, which probably indicates that - // the index is corrupted. Or not up-to-date. Log a warning, but only once, and only if seeing the - // error more that a number of times. - - var miss = Interlocked.CompareExchange(ref _examineIndexMiss, 0, 0); // volatile read - if (miss < ExamineIndexMissMax && Interlocked.Increment(ref _examineIndexMiss) == ExamineIndexMissMax) - LogHelper.Warn("Failed ({0} times) to retrieve medias from Examine index and had to load" - + " them from DB. This may indicate that the Examine index is corrupted.", - () => ExamineIndexMissMax); - - return ConvertFromIMedia(media); - } - - private const int ExamineIndexMissMax = 10; - private int _examineIndexMiss; - - internal CacheValues ConvertFromXPathNodeIterator(XPathNodeIterator media, int id) - { - if (media != null && media.Current != null) - { - return media.Current.Name.InvariantEquals("error") - ? null - : ConvertFromXPathNavigator(media.Current); - } - - LogHelper.Warn( - "Could not retrieve media {0} from Examine index or from legacy library.GetMedia method", - () => id); - - return null; - } - - internal CacheValues ConvertFromSearchResult(SearchResult searchResult) - { - //NOTE: Some fields will not be included if the config section for the internal index has been - //mucked around with. It should index everything and so the index definition should simply be: - // - - - var values = new Dictionary(searchResult.Fields); - //we need to ensure some fields exist, because of the above issue - if (!new[] { "template", "templateId" }.Any(values.ContainsKey)) - values.Add("template", 0.ToString()); - if (!new[] { "sortOrder" }.Any(values.ContainsKey)) - values.Add("sortOrder", 0.ToString()); - if (!new[] { "urlName" }.Any(values.ContainsKey)) - values.Add("urlName", ""); - if (!new[] { "nodeType" }.Any(values.ContainsKey)) - values.Add("nodeType", 0.ToString()); - if (!new[] { "creatorName" }.Any(values.ContainsKey)) - values.Add("creatorName", ""); - if (!new[] { "writerID" }.Any(values.ContainsKey)) - values.Add("writerID", 0.ToString()); - if (!new[] { "creatorID" }.Any(values.ContainsKey)) - values.Add("creatorID", 0.ToString()); - if (!new[] { "createDate" }.Any(values.ContainsKey)) - values.Add("createDate", default(DateTime).ToString("yyyy-MM-dd HH:mm:ss")); - if (!new[] { "level" }.Any(values.ContainsKey)) - { - values.Add("level", values["__Path"].Split(',').Length.ToString()); - } - - // because, migration - if (values.ContainsKey("key") == false) - values["key"] = Guid.Empty.ToString(); - - return new CacheValues - { - Values = values, - FromExamine = true - }; - - //var content = new DictionaryPublishedContent(values, - // d => d.ParentId != -1 //parent should be null if -1 - // ? GetUmbracoMedia(d.ParentId) - // : null, - // //callback to return the children of the current node - // d => GetChildrenMedia(d.Id), - // GetProperty, - // true); - //return content.CreateModel(); - } - - internal CacheValues ConvertFromXPathNavigator(XPathNavigator xpath, bool forceNav = false) - { - if (xpath == null) throw new ArgumentNullException("xpath"); - - var values = new Dictionary { { "nodeName", xpath.GetAttribute("nodeName", "") } }; - if (!UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema) - { - values["nodeTypeAlias"] = xpath.Name; - } - - var result = xpath.SelectChildren(XPathNodeType.Element); - //add the attributes e.g. id, parentId etc - if (result.Current != null && result.Current.HasAttributes) - { - if (result.Current.MoveToFirstAttribute()) - { - //checking for duplicate keys because of the 'nodeTypeAlias' might already be added above. - if (!values.ContainsKey(result.Current.Name)) - { - values[result.Current.Name] = result.Current.Value; - } - while (result.Current.MoveToNextAttribute()) - { - if (!values.ContainsKey(result.Current.Name)) - { - values[result.Current.Name] = result.Current.Value; - } - } - result.Current.MoveToParent(); - } - } - // because, migration - if (values.ContainsKey("key") == false) - values["key"] = Guid.Empty.ToString(); - //add the user props - while (result.MoveNext()) - { - if (result.Current != null && !result.Current.HasAttributes) - { - string value = result.Current.Value; - if (string.IsNullOrEmpty(value)) - { - if (result.Current.HasAttributes || result.Current.SelectChildren(XPathNodeType.Element).Count > 0) - { - value = result.Current.OuterXml; - } - } - values[result.Current.Name] = value; - } - } - - return new CacheValues - { - Values = values, - XPath = forceNav ? xpath : null // outside of tests we do NOT want to cache the navigator! - }; - - //var content = new DictionaryPublishedContent(values, - // d => d.ParentId != -1 //parent should be null if -1 - // ? GetUmbracoMedia(d.ParentId) - // : null, - // //callback to return the children of the current node based on the xml structure already found - // d => GetChildrenMedia(d.Id, xpath), - // GetProperty, - // false); - //return content.CreateModel(); - } - - internal CacheValues ConvertFromIMedia(IMedia media) - { - var values = new Dictionary(); - - var creator = _applicationContext.Services.UserService.GetProfileById(media.CreatorId); - var creatorName = creator == null ? "" : creator.Name; - - values["id"] = media.Id.ToString(); - values["key"] = media.Key.ToString(); - values["parentID"] = media.ParentId.ToString(); - values["level"] = media.Level.ToString(); - values["creatorID"] = media.CreatorId.ToString(); - values["creatorName"] = creatorName; - values["writerID"] = media.CreatorId.ToString(); - values["writerName"] = creatorName; - values["template"] = "0"; - values["urlName"] = ""; - values["sortOrder"] = media.SortOrder.ToString(); - values["createDate"] = media.CreateDate.ToString("yyyy-MM-dd HH:mm:ss"); - values["updateDate"] = media.UpdateDate.ToString("yyyy-MM-dd HH:mm:ss"); - values["nodeName"] = media.Name; - values["path"] = media.Path; - values["nodeType"] = media.ContentType.Id.ToString(); - values["nodeTypeAlias"] = media.ContentType.Alias; - - // add the user props - foreach (var prop in media.Properties) - values[prop.Alias] = prop.Value == null ? null : prop.Value.ToString(); - - return new CacheValues - { - Values = values - }; - } - - /// - /// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists - /// in the results, if it does not, then we'll have to revert to looking up in the db. - /// - /// - /// - /// - private IPublishedProperty GetProperty(DictionaryPublishedContent dd, string alias) - { - //lets check if the alias does not exist on the document. - //NOTE: Examine will not index empty values and we do not output empty XML Elements to the cache - either of these situations - // would mean that the property is missing from the collection whether we are getting the value from Examine or from the library media cache. - if (dd.Properties.All(x => x.PropertyTypeAlias.InvariantEquals(alias) == false)) - { - return null; - } - - if (dd.LoadedFromExamine) - { - //We are going to check for a special field however, that is because in some cases we store a 'Raw' - //value in the index such as for xml/html. - var rawValue = dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(UmbracoContentIndexer.RawFieldPrefix + alias)); - return rawValue - ?? dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); - } - - //if its not loaded from examine, then just return the property - return dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); - } - - /// - /// A Helper methods to return the children for media whther it is based on examine or xml - /// - /// - /// - /// - private IEnumerable GetChildrenMedia(int parentId, XPathNavigator xpath = null) - { - - //if there is no navigator, try examine first, then re-look it up - if (xpath == null) - { - var searchProvider = GetSearchProviderSafe(); - - if (searchProvider != null) - { - try - { - //first check in Examine as this is WAY faster - var criteria = searchProvider.CreateSearchCriteria("media"); - - var filter = criteria.ParentId(parentId).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); - //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 - - ISearchResults results; - - //we want to check if the indexer for this searcher has "sortOrder" flagged as sortable. - //if so, we'll use Lucene to do the sorting, if not we'll have to manually sort it (slower). - var indexer = GetIndexProviderSafe(); - var useLuceneSort = indexer != null && indexer.IndexerData.StandardFields.Any(x => x.Name.InvariantEquals("sortOrder") && x.EnableSorting); - if (useLuceneSort) - { - //we have a sortOrder field declared to be sorted, so we'll use Examine - results = searchProvider.Search( - filter.And().OrderBy(new SortableField("sortOrder", SortType.Int)).Compile()); - } - else - { - results = searchProvider.Search(filter.Compile()); - } - - if (results.Any()) - { - // var medias = results.Select(ConvertFromSearchResult); - var medias = results.Select(x => - { - int nid; - if (int.TryParse(x["__NodeId"], out nid) == false && int.TryParse(x["NodeId"], out nid) == false) - throw new Exception("Failed to extract NodeId from search result."); - var cacheValues = GetCacheValues(nid, id => ConvertFromSearchResult(x)); - return CreateFromCacheValues(cacheValues); - }); - - return useLuceneSort ? medias : medias.OrderBy(x => x.SortOrder); - } - else - { - //if there's no result then return null. Previously we defaulted back to library.GetMedia below - //but this will always get called for when we are getting descendents since many items won't have - //children and then we are hitting the database again! - //So instead we're going to rely on Examine to have the correct results like it should. - return Enumerable.Empty(); - } - } - catch (FileNotFoundException) - { - //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco - //See this thread: http://examine.cdodeplex.com/discussions/264341 - //Catch the exception here for the time being, and just fallback to GetMedia - } - } - - //falling back to get media - - var media = library.GetMedia(parentId, true); - if (media != null && media.Current != null) - { - xpath = media.Current; - } - else - { - return Enumerable.Empty(); - } - } - - var mediaList = new List(); - - // this is so bad, really - var item = xpath.Select("//*[@id='" + parentId + "']"); - if (item.Current == null) - return Enumerable.Empty(); - var items = item.Current.SelectChildren(XPathNodeType.Element); - - // and this does not work, because... meh - //var q = "//* [@id='" + parentId + "']/* [@id]"; - //var items = xpath.Select(q); - - foreach (XPathNavigator itemm in items) - { - int id; - if (int.TryParse(itemm.GetAttribute("id", ""), out id) == false) - continue; // wtf? - var captured = itemm; - var cacheValues = GetCacheValues(id, idd => ConvertFromXPathNavigator(captured)); - mediaList.Add(CreateFromCacheValues(cacheValues)); - } - - ////The xpath might be the whole xpath including the current ones ancestors so we need to select the current node - //var item = xpath.Select("//*[@id='" + parentId + "']"); - //if (item.Current == null) - //{ - // return Enumerable.Empty(); - //} - //var children = item.Current.SelectChildren(XPathNodeType.Element); - - //foreach(XPathNavigator x in children) - //{ - // //NOTE: I'm not sure why this is here, it is from legacy code of ExamineBackedMedia, but - // // will leave it here as it must have done something! - // if (x.Name != "contents") - // { - // //make sure it's actually a node, not a property - // if (!string.IsNullOrEmpty(x.GetAttribute("path", "")) && - // !string.IsNullOrEmpty(x.GetAttribute("id", ""))) - // { - // mediaList.Add(ConvertFromXPathNavigator(x)); - // } - // } - //} - - return mediaList; - } - - /// - /// An IPublishedContent that is represented all by a dictionary. - /// - /// - /// This is a helper class and definitely not intended for public use, it expects that all of the values required - /// to create an IPublishedContent exist in the dictionary by specific aliases. - /// - internal class DictionaryPublishedContent : PublishedContentWithKeyBase - { - // note: I'm not sure this class fully complies with IPublishedContent rules especially - // I'm not sure that _properties contains all properties including those without a value, - // neither that GetProperty will return a property without a value vs. null... @zpqrtbnk - - // List of properties that will appear in the XML and do not match - // anything in the ContentType, so they must be ignored. - private static readonly string[] IgnoredKeys = { "version", "isDoc" }; - - public DictionaryPublishedContent( - IDictionary valueDictionary, - Func getParent, - Func> getChildren, - Func getProperty, - XPathNavigator nav, - bool fromExamine) - { - if (valueDictionary == null) throw new ArgumentNullException("valueDictionary"); - if (getParent == null) throw new ArgumentNullException("getParent"); - if (getProperty == null) throw new ArgumentNullException("getProperty"); - - _getParent = new Lazy(() => getParent(ParentId)); - _getChildren = new Lazy>(() => getChildren(Id, nav)); - _getProperty = getProperty; - - LoadedFromExamine = fromExamine; - - ValidateAndSetProperty(valueDictionary, val => _id = int.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int! - ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key"); - // wtf are we dealing with templates for medias?! - ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); - ValidateAndSetProperty(valueDictionary, val => _sortOrder = int.Parse(val), "sortOrder"); - ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName", "__nodeName"); - ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName"); - ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", UmbracoContentIndexer.NodeTypeAliasFieldName); - ValidateAndSetProperty(valueDictionary, val => _documentTypeId = int.Parse(val), "nodeType"); - ValidateAndSetProperty(valueDictionary, val => _writerName = val, "writerName"); - ValidateAndSetProperty(valueDictionary, val => _creatorName = val, "creatorName", "writerName"); //this is a bit of a hack fix for: U4-1132 - ValidateAndSetProperty(valueDictionary, val => _writerId = int.Parse(val), "writerID"); - ValidateAndSetProperty(valueDictionary, val => _creatorId = int.Parse(val), "creatorID", "writerID"); //this is a bit of a hack fix for: U4-1132 - ValidateAndSetProperty(valueDictionary, val => _path = val, "path", "__Path"); - ValidateAndSetProperty(valueDictionary, val => _createDate = ParseDateTimeValue(val), "createDate"); - ValidateAndSetProperty(valueDictionary, val => _updateDate = ParseDateTimeValue(val), "updateDate"); - ValidateAndSetProperty(valueDictionary, val => _level = int.Parse(val), "level"); - ValidateAndSetProperty(valueDictionary, val => - { - int pId; - ParentId = -1; - if (int.TryParse(val, out pId)) - { - ParentId = pId; - } - }, "parentID"); - - _contentType = PublishedContentType.Get(PublishedItemType.Media, _documentTypeAlias); - _properties = new Collection(); - - //handle content type properties - //make sure we create them even if there's no value - foreach (var propertyType in _contentType.PropertyTypes) - { - var alias = propertyType.PropertyTypeAlias; - _keysAdded.Add(alias); - string value; - const bool isPreviewing = false; // false :: never preview a media - var property = valueDictionary.TryGetValue(alias, out value) == false || value == null - ? new XmlPublishedProperty(propertyType, isPreviewing) - : new XmlPublishedProperty(propertyType, isPreviewing, value); - _properties.Add(property); - } - - //loop through remaining values that haven't been applied - foreach (var i in valueDictionary.Where(x => - _keysAdded.Contains(x.Key) == false // not already processed - && IgnoredKeys.Contains(x.Key) == false)) // not ignorable - { - if (i.Key.InvariantStartsWith("__")) - { - // no type for that one, dunno how to convert - IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty); - _properties.Add(property); - } - else - { - // this is a property that does not correspond to anything, ignore and log - LogHelper.Warn("Dropping property \"" + i.Key + "\" because it does not belong to the content type."); - } - } - } - - private DateTime ParseDateTimeValue(string val) - { - if (LoadedFromExamine) - { - try - { - //we might need to parse the date time using Lucene converters - return DateTools.StringToDate(val); - } - catch (FormatException) - { - //swallow exception, its not formatted correctly so revert to just trying to parse - } - } - - return DateTime.Parse(val); - } - - /// - /// Flag to get/set if this was laoded from examine cache - /// - internal bool LoadedFromExamine { get; private set; } - - //private readonly Func _getParent; - private readonly Lazy _getParent; - //private readonly Func> _getChildren; - private readonly Lazy> _getChildren; - private readonly Func _getProperty; - - /// - /// Returns 'Media' as the item type - /// - public override PublishedItemType ItemType - { - get { return PublishedItemType.Media; } - } - - public override IPublishedContent Parent - { - get { return _getParent.Value; } - } - - public int ParentId { get; private set; } - public override int Id - { - get { return _id; } - } - - public override Guid Key { get { return _key; } } - - public override int TemplateId - { - get - { - //TODO: should probably throw a not supported exception since media doesn't actually support this. - return _templateId; - } - } - - public override int SortOrder - { - get { return _sortOrder; } - } - - public override string Name - { - get { return _name; } - } - - public override string UrlName - { - get { return _urlName; } - } - - public override string DocumentTypeAlias - { - get { return _documentTypeAlias; } - } - - public override int DocumentTypeId - { - get { return _documentTypeId; } - } - - public override string WriterName - { - get { return _writerName; } - } - - public override string CreatorName - { - get { return _creatorName; } - } - - public override int WriterId - { - get { return _writerId; } - } - - public override int CreatorId - { - get { return _creatorId; } - } - - public override string Path - { - get { return _path; } - } - - public override DateTime CreateDate - { - get { return _createDate; } - } - - public override DateTime UpdateDate - { - get { return _updateDate; } - } - - public override Guid Version - { - get { return _version; } - } - - public override int Level - { - get { return _level; } - } - - public override bool IsDraft - { - get { return false; } - } - - public override ICollection Properties - { - get { return _properties; } - } - - public override IEnumerable Children - { - get { return _getChildren.Value; } - } - - public override IPublishedProperty GetProperty(string alias) - { - return _getProperty(this, alias); - } - - public override PublishedContentType ContentType - { - get { return _contentType; } - } - - // override to implement cache - // cache at context level, ie once for the whole request - // but cache is not shared by requests because we wouldn't know how to clear it - public override IPublishedProperty GetProperty(string alias, bool recurse) - { - if (recurse == false) return GetProperty(alias); - - IPublishedProperty property; - string key = null; - var cache = UmbracoContextCache.Current; - - if (cache != null) - { - key = string.Format("RECURSIVE_PROPERTY::{0}::{1}", Id, alias.ToLowerInvariant()); - object o; - if (cache.TryGetValue(key, out o)) - { - property = o as IPublishedProperty; - if (property == null) - throw new InvalidOperationException("Corrupted cache."); - return property; - } - } - - // else get it for real, no cache - property = base.GetProperty(alias, true); - - if (cache != null) - cache[key] = property; - - return property; - } - - private readonly List _keysAdded = new List(); - private int _id; - private Guid _key; - private int _templateId; - private int _sortOrder; - private string _name; - private string _urlName; - private string _documentTypeAlias; - private int _documentTypeId; - private string _writerName; - private string _creatorName; - private int _writerId; - private int _creatorId; - private string _path; - private DateTime _createDate; - private DateTime _updateDate; - private Guid _version; - private int _level; - private readonly ICollection _properties; - private readonly PublishedContentType _contentType; - - private void ValidateAndSetProperty(IDictionary valueDictionary, Action setProperty, params string[] potentialKeys) - { - var key = potentialKeys.FirstOrDefault(x => valueDictionary.ContainsKey(x) && valueDictionary[x] != null); - if (key == null) - { - throw new FormatException("The valueDictionary is not formatted correctly and is missing any of the '" + string.Join(",", potentialKeys) + "' elements"); - } - - setProperty(valueDictionary[key]); - _keysAdded.Add(key); - } - } - - // REFACTORING - - // caching the basic atomic values - and the parent id - // but NOT caching actual parent nor children and NOT even - // the list of children ids - BUT caching the path - - internal class CacheValues - { - public IDictionary Values { get; set; } - public XPathNavigator XPath { get; set; } - public bool FromExamine { get; set; } - } - - public const string PublishedMediaCacheKey = "MediaCacheMeh."; - private const int PublishedMediaCacheTimespanSeconds = 4 * 60; // 4 mins - private static TimeSpan _publishedMediaCacheTimespan; - private static bool _publishedMediaCacheEnabled; - - private static void InitializeCacheConfig() - { - var value = ConfigurationManager.AppSettings["Umbraco.PublishedMediaCache.Seconds"]; - int seconds; - if (int.TryParse(value, out seconds) == false) - seconds = PublishedMediaCacheTimespanSeconds; - if (seconds > 0) - { - _publishedMediaCacheEnabled = true; - _publishedMediaCacheTimespan = TimeSpan.FromSeconds(seconds); - } - else - { - _publishedMediaCacheEnabled = false; - } - } - - internal IPublishedContent CreateFromCacheValues(CacheValues cacheValues) - { - var content = new DictionaryPublishedContent( - cacheValues.Values, - parentId => parentId < 0 ? null : GetUmbracoMedia(parentId), - GetChildrenMedia, - GetProperty, - cacheValues.XPath, // though, outside of tests, that should be null - cacheValues.FromExamine - ); - return content.CreateModel(); - } - - private static CacheValues GetCacheValues(int id, Func func) - { - if (_publishedMediaCacheEnabled == false) - return func(id); - - var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache; - var key = PublishedMediaCacheKey + id; - return (CacheValues)cache.GetCacheItem(key, () => func(id), _publishedMediaCacheTimespan); - } - - internal static void ClearCache(int id) - { - var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache; - var sid = id.ToString(); - var key = PublishedMediaCacheKey + sid; - - // we do clear a lot of things... but the cache refresher is somewhat - // convoluted and it's hard to tell what to clear exactly ;-( - - // clear the parent - NOT (why?) - //var exist = (CacheValues) cache.GetCacheItem(key); - //if (exist != null) - // cache.ClearCacheItem(PublishedMediaCacheKey + GetValuesValue(exist.Values, "parentID")); - - // clear the item - cache.ClearCacheItem(key); - - // clear all children - in case we moved and their path has changed - var fid = "/" + sid + "/"; - cache.ClearCacheObjectTypes((k, v) => - GetValuesValue(v.Values, "path", "__Path").Contains(fid)); - } - - private static string GetValuesValue(IDictionary d, params string[] keys) - { - string value = null; - var ignored = keys.Any(x => d.TryGetValue(x, out value)); - return value ?? ""; - } - } -} + + public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, string xpath, XPathVariable[] vars) + { + throw new NotImplementedException("PublishedMediaCache does not support XPath."); + } + + public virtual IPublishedContent GetSingleByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, XPathVariable[] vars) + { + throw new NotImplementedException("PublishedMediaCache does not support XPath."); + } + + public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, string xpath, XPathVariable[] vars) + { + throw new NotImplementedException("PublishedMediaCache does not support XPath."); + } + + public virtual IEnumerable GetByXPath(UmbracoContext umbracoContext, bool preview, XPathExpression xpath, XPathVariable[] vars) + { + throw new NotImplementedException("PublishedMediaCache does not support XPath."); + } + + public virtual XPathNavigator GetXPathNavigator(UmbracoContext umbracoContext, bool preview) + { + throw new NotImplementedException("PublishedMediaCache does not support XPath."); + } + + public bool XPathNavigatorIsNavigable { get { return false; } } + + public virtual bool HasContent(UmbracoContext context, bool preview) { throw new NotImplementedException(); } + + private ExamineManager GetExamineManagerSafe() + { + try + { + return ExamineManager.Instance; + } + catch (TypeInitializationException) + { + return null; + } + } + + private BaseIndexProvider GetIndexProviderSafe() + { + if (_indexProvider != null) + return _indexProvider; + + var eMgr = GetExamineManagerSafe(); + if (eMgr != null) + { + try + { + //by default use the InternalSearcher + var indexer = eMgr.IndexProviderCollection[Constants.Examine.InternalIndexer]; + if (indexer.IndexerData.IncludeNodeTypes.Any() || indexer.IndexerData.ExcludeNodeTypes.Any()) + { + LogHelper.Warn("The InternalIndexer for examine is configured incorrectly, it should not list any include/exclude node types or field names, it should simply be configured as: " + ""); + } + return indexer; + } + catch (Exception ex) + { + LogHelper.Error("Could not retrieve the InternalIndexer", ex); + //something didn't work, continue returning null. + } + } + return null; + } + + private BaseSearchProvider GetSearchProviderSafe() + { + if (_searchProvider != null) + return _searchProvider; + + var eMgr = GetExamineManagerSafe(); + if (eMgr != null) + { + try + { + //by default use the InternalSearcher + return eMgr.SearchProviderCollection[Constants.Examine.InternalSearcher]; + } + catch (FileNotFoundException) + { + //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco + //See this thread: http://examine.cdodeplex.com/discussions/264341 + //Catch the exception here for the time being, and just fallback to GetMedia + //TODO: Need to fix examine in LB scenarios! + } + catch (NullReferenceException) + { + //This will occur when the search provider cannot be initialized. In newer examine versions the initialization is lazy and therefore + // the manager will return the singleton without throwing initialization errors, however if examine isn't configured correctly a null + // reference error will occur because the examine settings are null. + } + catch (AlreadyClosedException) + { + //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot + //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. + } + } + return null; + } + + private IPublishedContent GetUmbracoMedia(int id) + { + // this recreates an IPublishedContent and model each time + // it is called, but at least it should NOT hit the database + // nor Lucene each time, relying on the memory cache instead + + if (id <= 0) return null; // fail fast + + var cacheValues = GetCacheValues(id, GetUmbracoMediaCacheValues); + + return cacheValues == null ? null : CreateFromCacheValues(cacheValues); + } + + private CacheValues GetUmbracoMediaCacheValues(int id) + { + var searchProvider = GetSearchProviderSafe(); + + if (searchProvider != null) + { + try + { + // first check in Examine as this is WAY faster + // + // the filter will create a query like this: + // +(+__NodeId:3113 -__Path:-1,-21,*) +__IndexType:media + // + // note that since the use of the wildcard, it automatically escapes it in Lucene. + + var criteria = searchProvider.CreateSearchCriteria("media"); + var filter = criteria.Id(id).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); + + var result = searchProvider.Search(filter.Compile()).FirstOrDefault(); + if (result != null) return ConvertFromSearchResult(result); + } + catch (Exception ex) + { + if (ex is FileNotFoundException) + { + //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco + //See this thread: http://examine.cdodeplex.com/discussions/264341 + //Catch the exception here for the time being, and just fallback to GetMedia + //TODO: Need to fix examine in LB scenarios! + LogHelper.Error("Could not load data from Examine index for media", ex); + } + else if (ex is AlreadyClosedException) + { + //If the app domain is shutting down and the site is under heavy load the index reader will be closed and it really cannot + //be re-opened since the app domain is shutting down. In this case we have no option but to try to load the data from the db. + LogHelper.Error("Could not load data from Examine index for media, the app domain is most likely in a shutdown state", ex); + } + else throw; + } + } + + // don't log a warning here, as it can flood the log in case of eg a media picker referencing a media + // that has been deleted, hence is not in the Examine index anymore (for a good reason). try to get + // the media from the service, first + + var media = ApplicationContext.Current.Services.MediaService.GetById(id); + if (media == null || media.Trashed) return null; // not found, ok + + // so, the media was not found in Examine's index *yet* it exists, which probably indicates that + // the index is corrupted. Or not up-to-date. Log a warning, but only once, and only if seeing the + // error more that a number of times. + + var miss = Interlocked.CompareExchange(ref _examineIndexMiss, 0, 0); // volatile read + if (miss < ExamineIndexMissMax && Interlocked.Increment(ref _examineIndexMiss) == ExamineIndexMissMax) + LogHelper.Warn("Failed ({0} times) to retrieve medias from Examine index and had to load" + + " them from DB. This may indicate that the Examine index is corrupted.", + () => ExamineIndexMissMax); + + return ConvertFromIMedia(media); + } + + private const int ExamineIndexMissMax = 10; + private int _examineIndexMiss; + + internal CacheValues ConvertFromXPathNodeIterator(XPathNodeIterator media, int id) + { + if (media != null && media.Current != null) + { + return media.Current.Name.InvariantEquals("error") + ? null + : ConvertFromXPathNavigator(media.Current); + } + + LogHelper.Warn( + "Could not retrieve media {0} from Examine index or from legacy library.GetMedia method", + () => id); + + return null; + } + + internal CacheValues ConvertFromSearchResult(SearchResult searchResult) + { + //NOTE: Some fields will not be included if the config section for the internal index has been + //mucked around with. It should index everything and so the index definition should simply be: + // + + + var values = new Dictionary(searchResult.Fields); + //we need to ensure some fields exist, because of the above issue + if (!new[] { "template", "templateId" }.Any(values.ContainsKey)) + values.Add("template", 0.ToString()); + if (!new[] { "sortOrder" }.Any(values.ContainsKey)) + values.Add("sortOrder", 0.ToString()); + if (!new[] { "urlName" }.Any(values.ContainsKey)) + values.Add("urlName", ""); + if (!new[] { "nodeType" }.Any(values.ContainsKey)) + values.Add("nodeType", 0.ToString()); + if (!new[] { "creatorName" }.Any(values.ContainsKey)) + values.Add("creatorName", ""); + if (!new[] { "writerID" }.Any(values.ContainsKey)) + values.Add("writerID", 0.ToString()); + if (!new[] { "creatorID" }.Any(values.ContainsKey)) + values.Add("creatorID", 0.ToString()); + if (!new[] { "createDate" }.Any(values.ContainsKey)) + values.Add("createDate", default(DateTime).ToString("yyyy-MM-dd HH:mm:ss")); + if (!new[] { "level" }.Any(values.ContainsKey)) + { + values.Add("level", values["__Path"].Split(',').Length.ToString()); + } + + // because, migration + if (values.ContainsKey("key") == false) + values["key"] = Guid.Empty.ToString(); + + return new CacheValues + { + Values = values, + FromExamine = true + }; + + //var content = new DictionaryPublishedContent(values, + // d => d.ParentId != -1 //parent should be null if -1 + // ? GetUmbracoMedia(d.ParentId) + // : null, + // //callback to return the children of the current node + // d => GetChildrenMedia(d.Id), + // GetProperty, + // true); + //return content.CreateModel(); + } + + internal CacheValues ConvertFromXPathNavigator(XPathNavigator xpath, bool forceNav = false) + { + if (xpath == null) throw new ArgumentNullException("xpath"); + + var values = new Dictionary { { "nodeName", xpath.GetAttribute("nodeName", "") } }; + if (!UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema) + { + values["nodeTypeAlias"] = xpath.Name; + } + + var result = xpath.SelectChildren(XPathNodeType.Element); + //add the attributes e.g. id, parentId etc + if (result.Current != null && result.Current.HasAttributes) + { + if (result.Current.MoveToFirstAttribute()) + { + //checking for duplicate keys because of the 'nodeTypeAlias' might already be added above. + if (!values.ContainsKey(result.Current.Name)) + { + values[result.Current.Name] = result.Current.Value; + } + while (result.Current.MoveToNextAttribute()) + { + if (!values.ContainsKey(result.Current.Name)) + { + values[result.Current.Name] = result.Current.Value; + } + } + result.Current.MoveToParent(); + } + } + // because, migration + if (values.ContainsKey("key") == false) + values["key"] = Guid.Empty.ToString(); + //add the user props + while (result.MoveNext()) + { + if (result.Current != null && !result.Current.HasAttributes) + { + string value = result.Current.Value; + if (string.IsNullOrEmpty(value)) + { + if (result.Current.HasAttributes || result.Current.SelectChildren(XPathNodeType.Element).Count > 0) + { + value = result.Current.OuterXml; + } + } + values[result.Current.Name] = value; + } + } + + return new CacheValues + { + Values = values, + XPath = forceNav ? xpath : null // outside of tests we do NOT want to cache the navigator! + }; + + //var content = new DictionaryPublishedContent(values, + // d => d.ParentId != -1 //parent should be null if -1 + // ? GetUmbracoMedia(d.ParentId) + // : null, + // //callback to return the children of the current node based on the xml structure already found + // d => GetChildrenMedia(d.Id, xpath), + // GetProperty, + // false); + //return content.CreateModel(); + } + + internal CacheValues ConvertFromIMedia(IMedia media) + { + var values = new Dictionary(); + + var creator = _applicationContext.Services.UserService.GetProfileById(media.CreatorId); + var creatorName = creator == null ? "" : creator.Name; + + values["id"] = media.Id.ToString(); + values["key"] = media.Key.ToString(); + values["parentID"] = media.ParentId.ToString(); + values["level"] = media.Level.ToString(); + values["creatorID"] = media.CreatorId.ToString(); + values["creatorName"] = creatorName; + values["writerID"] = media.CreatorId.ToString(); + values["writerName"] = creatorName; + values["template"] = "0"; + values["urlName"] = ""; + values["sortOrder"] = media.SortOrder.ToString(); + values["createDate"] = media.CreateDate.ToString("yyyy-MM-dd HH:mm:ss"); + values["updateDate"] = media.UpdateDate.ToString("yyyy-MM-dd HH:mm:ss"); + values["nodeName"] = media.Name; + values["path"] = media.Path; + values["nodeType"] = media.ContentType.Id.ToString(); + values["nodeTypeAlias"] = media.ContentType.Alias; + + // add the user props + foreach (var prop in media.Properties) + values[prop.Alias] = prop.Value == null ? null : prop.Value.ToString(); + + return new CacheValues + { + Values = values + }; + } + + /// + /// We will need to first check if the document was loaded by Examine, if so we'll need to check if this property exists + /// in the results, if it does not, then we'll have to revert to looking up in the db. + /// + /// + /// + /// + private IPublishedProperty GetProperty(DictionaryPublishedContent dd, string alias) + { + //lets check if the alias does not exist on the document. + //NOTE: Examine will not index empty values and we do not output empty XML Elements to the cache - either of these situations + // would mean that the property is missing from the collection whether we are getting the value from Examine or from the library media cache. + if (dd.Properties.All(x => x.PropertyTypeAlias.InvariantEquals(alias) == false)) + { + return null; + } + + if (dd.LoadedFromExamine) + { + //We are going to check for a special field however, that is because in some cases we store a 'Raw' + //value in the index such as for xml/html. + var rawValue = dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(UmbracoContentIndexer.RawFieldPrefix + alias)); + return rawValue + ?? dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + } + + //if its not loaded from examine, then just return the property + return dd.Properties.FirstOrDefault(x => x.PropertyTypeAlias.InvariantEquals(alias)); + } + + /// + /// A Helper methods to return the children for media whther it is based on examine or xml + /// + /// + /// + /// + private IEnumerable GetChildrenMedia(int parentId, XPathNavigator xpath = null) + { + + //if there is no navigator, try examine first, then re-look it up + if (xpath == null) + { + var searchProvider = GetSearchProviderSafe(); + + if (searchProvider != null) + { + try + { + //first check in Examine as this is WAY faster + var criteria = searchProvider.CreateSearchCriteria("media"); + + var filter = criteria.ParentId(parentId).Not().Field(UmbracoContentIndexer.IndexPathFieldName, "-1,-21,".MultipleCharacterWildcard()); + //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 + + ISearchResults results; + + //we want to check if the indexer for this searcher has "sortOrder" flagged as sortable. + //if so, we'll use Lucene to do the sorting, if not we'll have to manually sort it (slower). + var indexer = GetIndexProviderSafe(); + var useLuceneSort = indexer != null && indexer.IndexerData.StandardFields.Any(x => x.Name.InvariantEquals("sortOrder") && x.EnableSorting); + if (useLuceneSort) + { + //we have a sortOrder field declared to be sorted, so we'll use Examine + results = searchProvider.Search( + filter.And().OrderBy(new SortableField("sortOrder", SortType.Int)).Compile()); + } + else + { + results = searchProvider.Search(filter.Compile()); + } + + if (results.Any()) + { + // var medias = results.Select(ConvertFromSearchResult); + var medias = results.Select(x => + { + int nid; + if (int.TryParse(x["__NodeId"], out nid) == false && int.TryParse(x["NodeId"], out nid) == false) + throw new Exception("Failed to extract NodeId from search result."); + var cacheValues = GetCacheValues(nid, id => ConvertFromSearchResult(x)); + return CreateFromCacheValues(cacheValues); + }); + + return useLuceneSort ? medias : medias.OrderBy(x => x.SortOrder); + } + else + { + //if there's no result then return null. Previously we defaulted back to library.GetMedia below + //but this will always get called for when we are getting descendents since many items won't have + //children and then we are hitting the database again! + //So instead we're going to rely on Examine to have the correct results like it should. + return Enumerable.Empty(); + } + } + catch (FileNotFoundException) + { + //Currently examine is throwing FileNotFound exceptions when we have a loadbalanced filestore and a node is published in umbraco + //See this thread: http://examine.cdodeplex.com/discussions/264341 + //Catch the exception here for the time being, and just fallback to GetMedia + } + } + + //falling back to get media + + var media = library.GetMedia(parentId, true); + if (media != null && media.Current != null) + { + xpath = media.Current; + } + else + { + return Enumerable.Empty(); + } + } + + var mediaList = new List(); + + // this is so bad, really + var item = xpath.Select("//*[@id='" + parentId + "']"); + if (item.Current == null) + return Enumerable.Empty(); + var items = item.Current.SelectChildren(XPathNodeType.Element); + + // and this does not work, because... meh + //var q = "//* [@id='" + parentId + "']/* [@id]"; + //var items = xpath.Select(q); + + foreach (XPathNavigator itemm in items) + { + int id; + if (int.TryParse(itemm.GetAttribute("id", ""), out id) == false) + continue; // wtf? + var captured = itemm; + var cacheValues = GetCacheValues(id, idd => ConvertFromXPathNavigator(captured)); + mediaList.Add(CreateFromCacheValues(cacheValues)); + } + + ////The xpath might be the whole xpath including the current ones ancestors so we need to select the current node + //var item = xpath.Select("//*[@id='" + parentId + "']"); + //if (item.Current == null) + //{ + // return Enumerable.Empty(); + //} + //var children = item.Current.SelectChildren(XPathNodeType.Element); + + //foreach(XPathNavigator x in children) + //{ + // //NOTE: I'm not sure why this is here, it is from legacy code of ExamineBackedMedia, but + // // will leave it here as it must have done something! + // if (x.Name != "contents") + // { + // //make sure it's actually a node, not a property + // if (!string.IsNullOrEmpty(x.GetAttribute("path", "")) && + // !string.IsNullOrEmpty(x.GetAttribute("id", ""))) + // { + // mediaList.Add(ConvertFromXPathNavigator(x)); + // } + // } + //} + + return mediaList; + } + + /// + /// An IPublishedContent that is represented all by a dictionary. + /// + /// + /// This is a helper class and definitely not intended for public use, it expects that all of the values required + /// to create an IPublishedContent exist in the dictionary by specific aliases. + /// + internal class DictionaryPublishedContent : PublishedContentWithKeyBase + { + // note: I'm not sure this class fully complies with IPublishedContent rules especially + // I'm not sure that _properties contains all properties including those without a value, + // neither that GetProperty will return a property without a value vs. null... @zpqrtbnk + + // List of properties that will appear in the XML and do not match + // anything in the ContentType, so they must be ignored. + private static readonly string[] IgnoredKeys = { "version", "isDoc" }; + + public DictionaryPublishedContent( + IDictionary valueDictionary, + Func getParent, + Func> getChildren, + Func getProperty, + XPathNavigator nav, + bool fromExamine) + { + if (valueDictionary == null) throw new ArgumentNullException("valueDictionary"); + if (getParent == null) throw new ArgumentNullException("getParent"); + if (getProperty == null) throw new ArgumentNullException("getProperty"); + + _getParent = new Lazy(() => getParent(ParentId)); + _getChildren = new Lazy>(() => getChildren(Id, nav)); + _getProperty = getProperty; + + LoadedFromExamine = fromExamine; + + ValidateAndSetProperty(valueDictionary, val => _id = int.Parse(val), "id", "nodeId", "__NodeId"); //should validate the int! + ValidateAndSetProperty(valueDictionary, val => _key = Guid.Parse(val), "key"); + // wtf are we dealing with templates for medias?! + ValidateAndSetProperty(valueDictionary, val => _templateId = int.Parse(val), "template", "templateId"); + ValidateAndSetProperty(valueDictionary, val => _sortOrder = int.Parse(val), "sortOrder"); + ValidateAndSetProperty(valueDictionary, val => _name = val, "nodeName", "__nodeName"); + ValidateAndSetProperty(valueDictionary, val => _urlName = val, "urlName"); + ValidateAndSetProperty(valueDictionary, val => _documentTypeAlias = val, "nodeTypeAlias", UmbracoContentIndexer.NodeTypeAliasFieldName); + ValidateAndSetProperty(valueDictionary, val => _documentTypeId = int.Parse(val), "nodeType"); + ValidateAndSetProperty(valueDictionary, val => _writerName = val, "writerName"); + ValidateAndSetProperty(valueDictionary, val => _creatorName = val, "creatorName", "writerName"); //this is a bit of a hack fix for: U4-1132 + ValidateAndSetProperty(valueDictionary, val => _writerId = int.Parse(val), "writerID"); + ValidateAndSetProperty(valueDictionary, val => _creatorId = int.Parse(val), "creatorID", "writerID"); //this is a bit of a hack fix for: U4-1132 + ValidateAndSetProperty(valueDictionary, val => _path = val, "path", "__Path"); + ValidateAndSetProperty(valueDictionary, val => _createDate = ParseDateTimeValue(val), "createDate"); + ValidateAndSetProperty(valueDictionary, val => _updateDate = ParseDateTimeValue(val), "updateDate"); + ValidateAndSetProperty(valueDictionary, val => _level = int.Parse(val), "level"); + ValidateAndSetProperty(valueDictionary, val => + { + int pId; + ParentId = -1; + if (int.TryParse(val, out pId)) + { + ParentId = pId; + } + }, "parentID"); + + _contentType = PublishedContentType.Get(PublishedItemType.Media, _documentTypeAlias); + _properties = new Collection(); + + //handle content type properties + //make sure we create them even if there's no value + foreach (var propertyType in _contentType.PropertyTypes) + { + var alias = propertyType.PropertyTypeAlias; + _keysAdded.Add(alias); + string value; + const bool isPreviewing = false; // false :: never preview a media + var property = valueDictionary.TryGetValue(alias, out value) == false || value == null + ? new XmlPublishedProperty(propertyType, isPreviewing) + : new XmlPublishedProperty(propertyType, isPreviewing, value); + _properties.Add(property); + } + + //loop through remaining values that haven't been applied + foreach (var i in valueDictionary.Where(x => + _keysAdded.Contains(x.Key) == false // not already processed + && IgnoredKeys.Contains(x.Key) == false)) // not ignorable + { + if (i.Key.InvariantStartsWith("__")) + { + // no type for that one, dunno how to convert + IPublishedProperty property = new PropertyResult(i.Key, i.Value, PropertyResultType.CustomProperty); + _properties.Add(property); + } + else + { + // this is a property that does not correspond to anything, ignore and log + LogHelper.Warn("Dropping property \"" + i.Key + "\" because it does not belong to the content type."); + } + } + } + + private DateTime ParseDateTimeValue(string val) + { + if (LoadedFromExamine) + { + try + { + //we might need to parse the date time using Lucene converters + return DateTools.StringToDate(val); + } + catch (FormatException) + { + //swallow exception, its not formatted correctly so revert to just trying to parse + } + } + + return DateTime.Parse(val); + } + + /// + /// Flag to get/set if this was laoded from examine cache + /// + internal bool LoadedFromExamine { get; private set; } + + //private readonly Func _getParent; + private readonly Lazy _getParent; + //private readonly Func> _getChildren; + private readonly Lazy> _getChildren; + private readonly Func _getProperty; + + /// + /// Returns 'Media' as the item type + /// + public override PublishedItemType ItemType + { + get { return PublishedItemType.Media; } + } + + public override IPublishedContent Parent + { + get { return _getParent.Value; } + } + + public int ParentId { get; private set; } + public override int Id + { + get { return _id; } + } + + public override Guid Key { get { return _key; } } + + public override int TemplateId + { + get + { + //TODO: should probably throw a not supported exception since media doesn't actually support this. + return _templateId; + } + } + + public override int SortOrder + { + get { return _sortOrder; } + } + + public override string Name + { + get { return _name; } + } + + public override string UrlName + { + get { return _urlName; } + } + + public override string DocumentTypeAlias + { + get { return _documentTypeAlias; } + } + + public override int DocumentTypeId + { + get { return _documentTypeId; } + } + + public override string WriterName + { + get { return _writerName; } + } + + public override string CreatorName + { + get { return _creatorName; } + } + + public override int WriterId + { + get { return _writerId; } + } + + public override int CreatorId + { + get { return _creatorId; } + } + + public override string Path + { + get { return _path; } + } + + public override DateTime CreateDate + { + get { return _createDate; } + } + + public override DateTime UpdateDate + { + get { return _updateDate; } + } + + public override Guid Version + { + get { return _version; } + } + + public override int Level + { + get { return _level; } + } + + public override bool IsDraft + { + get { return false; } + } + + public override ICollection Properties + { + get { return _properties; } + } + + public override IEnumerable Children + { + get { return _getChildren.Value; } + } + + public override IPublishedProperty GetProperty(string alias) + { + return _getProperty(this, alias); + } + + public override PublishedContentType ContentType + { + get { return _contentType; } + } + + // override to implement cache + // cache at context level, ie once for the whole request + // but cache is not shared by requests because we wouldn't know how to clear it + public override IPublishedProperty GetProperty(string alias, bool recurse) + { + if (recurse == false) return GetProperty(alias); + + IPublishedProperty property; + string key = null; + var cache = UmbracoContextCache.Current; + + if (cache != null) + { + key = string.Format("RECURSIVE_PROPERTY::{0}::{1}", Id, alias.ToLowerInvariant()); + object o; + if (cache.TryGetValue(key, out o)) + { + property = o as IPublishedProperty; + if (property == null) + throw new InvalidOperationException("Corrupted cache."); + return property; + } + } + + // else get it for real, no cache + property = base.GetProperty(alias, true); + + if (cache != null) + cache[key] = property; + + return property; + } + + private readonly List _keysAdded = new List(); + private int _id; + private Guid _key; + private int _templateId; + private int _sortOrder; + private string _name; + private string _urlName; + private string _documentTypeAlias; + private int _documentTypeId; + private string _writerName; + private string _creatorName; + private int _writerId; + private int _creatorId; + private string _path; + private DateTime _createDate; + private DateTime _updateDate; + private Guid _version; + private int _level; + private readonly ICollection _properties; + private readonly PublishedContentType _contentType; + + private void ValidateAndSetProperty(IDictionary valueDictionary, Action setProperty, params string[] potentialKeys) + { + var key = potentialKeys.FirstOrDefault(x => valueDictionary.ContainsKey(x) && valueDictionary[x] != null); + if (key == null) + { + throw new FormatException("The valueDictionary is not formatted correctly and is missing any of the '" + string.Join(",", potentialKeys) + "' elements"); + } + + setProperty(valueDictionary[key]); + _keysAdded.Add(key); + } + } + + // REFACTORING + + // caching the basic atomic values - and the parent id + // but NOT caching actual parent nor children and NOT even + // the list of children ids - BUT caching the path + + internal class CacheValues + { + public IDictionary Values { get; set; } + public XPathNavigator XPath { get; set; } + public bool FromExamine { get; set; } + } + + public const string PublishedMediaCacheKey = "MediaCacheMeh."; + private const int PublishedMediaCacheTimespanSeconds = 4 * 60; // 4 mins + private static TimeSpan _publishedMediaCacheTimespan; + private static bool _publishedMediaCacheEnabled; + + private static void InitializeCacheConfig() + { + var value = ConfigurationManager.AppSettings["Umbraco.PublishedMediaCache.Seconds"]; + int seconds; + if (int.TryParse(value, out seconds) == false) + seconds = PublishedMediaCacheTimespanSeconds; + if (seconds > 0) + { + _publishedMediaCacheEnabled = true; + _publishedMediaCacheTimespan = TimeSpan.FromSeconds(seconds); + } + else + { + _publishedMediaCacheEnabled = false; + } + } + + internal IPublishedContent CreateFromCacheValues(CacheValues cacheValues) + { + var content = new DictionaryPublishedContent( + cacheValues.Values, + parentId => parentId < 0 ? null : GetUmbracoMedia(parentId), + GetChildrenMedia, + GetProperty, + cacheValues.XPath, // though, outside of tests, that should be null + cacheValues.FromExamine + ); + return content.CreateModel(); + } + + private static CacheValues GetCacheValues(int id, Func func) + { + if (_publishedMediaCacheEnabled == false) + return func(id); + + var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache; + var key = PublishedMediaCacheKey + id; + return (CacheValues)cache.GetCacheItem(key, () => func(id), _publishedMediaCacheTimespan); + } + + internal static void ClearCache(int id) + { + var cache = ApplicationContext.Current.ApplicationCache.RuntimeCache; + var sid = id.ToString(); + var key = PublishedMediaCacheKey + sid; + + // we do clear a lot of things... but the cache refresher is somewhat + // convoluted and it's hard to tell what to clear exactly ;-( + + // clear the parent - NOT (why?) + //var exist = (CacheValues) cache.GetCacheItem(key); + //if (exist != null) + // cache.ClearCacheItem(PublishedMediaCacheKey + GetValuesValue(exist.Values, "parentID")); + + // clear the item + cache.ClearCacheItem(key); + + // clear all children - in case we moved and their path has changed + var fid = "/" + sid + "/"; + cache.ClearCacheObjectTypes((k, v) => + GetValuesValue(v.Values, "path", "__Path").Contains(fid)); + } + + private static string GetValuesValue(IDictionary d, params string[] keys) + { + string value = null; + var ignored = keys.Any(x => d.TryGetValue(x, out value)); + return value ?? ""; + } + } +} diff --git a/src/Umbraco.Web/Search/ExamineEvents.cs b/src/Umbraco.Web/Search/ExamineEvents.cs index cf4d6e33d4..0c8eda7ecb 100644 --- a/src/Umbraco.Web/Search/ExamineEvents.cs +++ b/src/Umbraco.Web/Search/ExamineEvents.cs @@ -1,729 +1,736 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Xml; -using System.Xml.Linq; -using Examine; -using Examine.LuceneEngine; -using Lucene.Net.Documents; -using Umbraco.Core; -using Umbraco.Core.Cache; -using Umbraco.Core.Logging; -using Umbraco.Core.Models; -using Umbraco.Core.Scoping; -using Umbraco.Core.Sync; -using Umbraco.Web.Cache; -using UmbracoExamine; -using Content = umbraco.cms.businesslogic.Content; -using Document = umbraco.cms.businesslogic.web.Document; - -namespace Umbraco.Web.Search -{ - /// - /// Used to wire up events for Examine - /// - public sealed class ExamineEvents : ApplicationEventHandler - { - // the default enlist priority is 100 - // enlist with a lower priority to ensure that anything "default" runs after us - // but greater that SafeXmlReaderWriter priority which is 60 - private const int EnlistPriority = 80; - - /// - /// Once the application has started we should bind to all events and initialize the providers. - /// - /// - /// - /// - /// We need to do this on the Started event as to guarantee that all resolvers are setup properly. - /// - protected override void ApplicationStarted(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext) - { - LogHelper.Info("Initializing Examine and binding to business logic events"); - - var registeredProviders = ExamineManager.Instance.IndexProviderCollection - .OfType().Count(x => x.EnableDefaultEventHandler); - - LogHelper.Info("Adding examine event handlers for index providers: {0}", () => registeredProviders); - - //don't bind event handlers if we're not suppose to listen - if (registeredProviders == 0) - return; - - //Bind to distributed cache events - this ensures that this logic occurs on ALL servers that are taking part - // in a load balanced environment. - CacheRefresherBase.CacheUpdated += UnpublishedPageCacheRefresherCacheUpdated; - CacheRefresherBase.CacheUpdated += PublishedPageCacheRefresherCacheUpdated; - CacheRefresherBase.CacheUpdated += MediaCacheRefresherCacheUpdated; - CacheRefresherBase.CacheUpdated += MemberCacheRefresherCacheUpdated; - CacheRefresherBase.CacheUpdated += ContentTypeCacheRefresherCacheUpdated; - - var contentIndexer = ExamineManager.Instance.IndexProviderCollection[Constants.Examine.InternalIndexer] as UmbracoContentIndexer; - if (contentIndexer != null) - { - contentIndexer.DocumentWriting += IndexerDocumentWriting; - } - var memberIndexer = ExamineManager.Instance.IndexProviderCollection[Constants.Examine.InternalMemberIndexer] as UmbracoMemberIndexer; - if (memberIndexer != null) - { - memberIndexer.DocumentWriting += IndexerDocumentWriting; - } - } - - /// - /// This is used to refresh content indexers IndexData based on the DataService whenever a content type is changed since - /// properties may have been added/removed, then we need to re-index any required data if aliases have been changed - /// - /// - /// - /// - /// See: http://issues.umbraco.org/issue/U4-4798, http://issues.umbraco.org/issue/U4-7833 - /// - static void ContentTypeCacheRefresherCacheUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs e) - { - if (Suspendable.ExamineEvents.CanIndex == false) - return; - - var indexersToUpdated = ExamineManager.Instance.IndexProviderCollection.OfType(); - foreach (var provider in indexersToUpdated) - { - provider.RefreshIndexerDataFromDataService(); - } - - if (e.MessageType == MessageType.RefreshByJson) - { - var contentTypesChanged = new HashSet(); - var mediaTypesChanged = new HashSet(); - var memberTypesChanged = new HashSet(); - - var payloads = ContentTypeCacheRefresher.DeserializeFromJsonPayload(e.MessageObject.ToString()); - foreach (var payload in payloads) - { - if (payload.IsNew == false - && (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved || payload.PropertyTypeAliasChanged)) - { - //if we get here it means that some aliases have changed and the indexes for those particular doc types will need to be updated - if (payload.Type == typeof(IContentType).Name) - { - //if it is content - contentTypesChanged.Add(payload.Alias); - } - else if (payload.Type == typeof(IMediaType).Name) - { - //if it is media - mediaTypesChanged.Add(payload.Alias); - } - else if (payload.Type == typeof(IMemberType).Name) - { - //if it is members - memberTypesChanged.Add(payload.Alias); - } - } - } - - //TODO: We need to update Examine to support re-indexing multiple items at once instead of one by one which will speed up - // the re-indexing process, we don't want to revert to rebuilding the whole thing! - - if (contentTypesChanged.Count > 0) - { - foreach (var alias in contentTypesChanged) - { - var ctType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias); - if (ctType != null) - { - var contentItems = ApplicationContext.Current.Services.ContentService.GetContentOfContentType(ctType.Id); - foreach (var contentItem in contentItems) - { - ReIndexForContent(contentItem, contentItem.HasPublishedVersion && contentItem.Trashed == false); - } - } - } - } - if (mediaTypesChanged.Count > 0) - { - foreach (var alias in mediaTypesChanged) - { - var ctType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias); - if (ctType != null) - { - var mediaItems = ApplicationContext.Current.Services.MediaService.GetMediaOfMediaType(ctType.Id); - foreach (var mediaItem in mediaItems) - { - ReIndexForMedia(mediaItem, mediaItem.Trashed == false); - } - } - } - } - if (memberTypesChanged.Count > 0) - { - foreach (var alias in memberTypesChanged) - { - var ctType = ApplicationContext.Current.Services.MemberTypeService.Get(alias); - if (ctType != null) - { - var memberItems = ApplicationContext.Current.Services.MemberService.GetMembersByMemberType(ctType.Id); - foreach (var memberItem in memberItems) - { - ReIndexForMember(memberItem); - } - } - } - } - } - - } - - static void MemberCacheRefresherCacheUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs e) - { - if (Suspendable.ExamineEvents.CanIndex == false) - return; - - switch (e.MessageType) - { - case MessageType.RefreshById: - var c1 = ApplicationContext.Current.Services.MemberService.GetById((int)e.MessageObject); - if (c1 != null) - { - ReIndexForMember(c1); - } - break; - case MessageType.RemoveById: - - // This is triggered when the item is permanently deleted - - DeleteIndexForEntity((int)e.MessageObject, false); - break; - case MessageType.RefreshByInstance: - var c3 = e.MessageObject as IMember; - if (c3 != null) - { - ReIndexForMember(c3); - } - break; - case MessageType.RemoveByInstance: - - // This is triggered when the item is permanently deleted - - var c4 = e.MessageObject as IMember; - if (c4 != null) - { - DeleteIndexForEntity(c4.Id, false); - } - break; - case MessageType.RefreshAll: - case MessageType.RefreshByJson: - default: - //We don't support these, these message types will not fire for unpublished content - break; - } - } - - /// - /// Handles index management for all media events - basically handling saving/copying/trashing/deleting - /// - /// - /// - static void MediaCacheRefresherCacheUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs e) - { - if (Suspendable.ExamineEvents.CanIndex == false) - return; - - switch (e.MessageType) - { - case MessageType.RefreshById: - var c1 = ApplicationContext.Current.Services.MediaService.GetById((int)e.MessageObject); - if (c1 != null) - { - ReIndexForMedia(c1, c1.Trashed == false); - } - break; - case MessageType.RemoveById: - var c2 = ApplicationContext.Current.Services.MediaService.GetById((int)e.MessageObject); - if (c2 != null) - { - //This is triggered when the item has trashed. - // So we need to delete the index from all indexes not supporting unpublished content. - - DeleteIndexForEntity(c2.Id, true); - - //We then need to re-index this item for all indexes supporting unpublished content - - ReIndexForMedia(c2, false); - } - break; - case MessageType.RefreshByJson: - - var jsonPayloads = MediaCacheRefresher.DeserializeFromJsonPayload((string)e.MessageObject); - if (jsonPayloads.Any()) - { - foreach (var payload in jsonPayloads) - { - switch (payload.Operation) - { - case MediaCacheRefresher.OperationType.Saved: - var media1 = ApplicationContext.Current.Services.MediaService.GetById(payload.Id); - if (media1 != null) - { - ReIndexForMedia(media1, media1.Trashed == false); - } - break; - case MediaCacheRefresher.OperationType.Trashed: - - //keep if trashed for indexes supporting unpublished - //(delete the index from all indexes not supporting unpublished content) - - DeleteIndexForEntity(payload.Id, true); - - //We then need to re-index this item for all indexes supporting unpublished content - var media2 = ApplicationContext.Current.Services.MediaService.GetById(payload.Id); - if (media2 != null) - { - ReIndexForMedia(media2, false); - } - - break; - case MediaCacheRefresher.OperationType.Deleted: - - //permanently remove from all indexes - - DeleteIndexForEntity(payload.Id, false); - - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - } - - break; - case MessageType.RefreshByInstance: - case MessageType.RemoveByInstance: - case MessageType.RefreshAll: - default: - //We don't support these, these message types will not fire for media - break; - } - } - - /// - /// Handles index management for all published content events - basically handling published/unpublished - /// - /// - /// - /// - /// This will execute on all servers taking part in load balancing - /// - static void PublishedPageCacheRefresherCacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs e) - { - if (Suspendable.ExamineEvents.CanIndex == false) - return; - - switch (e.MessageType) - { - case MessageType.RefreshById: - var c1 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject); - if (c1 != null) - { - ReIndexForContent(c1, true); - } - break; - case MessageType.RemoveById: - - //This is triggered when the item has been unpublished or trashed (which also performs an unpublish). - - var c2 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject); - if (c2 != null) - { - // So we need to delete the index from all indexes not supporting unpublished content. - - DeleteIndexForEntity(c2.Id, true); - - // We then need to re-index this item for all indexes supporting unpublished content - - ReIndexForContent(c2, false); - } - break; - case MessageType.RefreshByInstance: - var c3 = e.MessageObject as IContent; - if (c3 != null) - { - ReIndexForContent(c3, true); - } - break; - case MessageType.RemoveByInstance: - - //This is triggered when the item has been unpublished or trashed (which also performs an unpublish). - - var c4 = e.MessageObject as IContent; - if (c4 != null) - { - // So we need to delete the index from all indexes not supporting unpublished content. - - DeleteIndexForEntity(c4.Id, true); - - // We then need to re-index this item for all indexes supporting unpublished content - - ReIndexForContent(c4, false); - } - break; - case MessageType.RefreshAll: - case MessageType.RefreshByJson: - default: - //We don't support these for examine indexing - break; - } - } - - /// - /// Handles index management for all unpublished content events - basically handling saving/copying/deleting - /// - /// - /// - /// - /// This will execute on all servers taking part in load balancing - /// - static void UnpublishedPageCacheRefresherCacheUpdated(UnpublishedPageCacheRefresher sender, CacheRefresherEventArgs e) - { - if (Suspendable.ExamineEvents.CanIndex == false) - return; - - switch (e.MessageType) - { - case MessageType.RefreshById: - var c1 = ApplicationContext.Current.Services.ContentService.GetById((int) e.MessageObject); - if (c1 != null) - { - ReIndexForContent(c1, false); - } - break; - case MessageType.RemoveById: - - // This is triggered when the item is permanently deleted - - DeleteIndexForEntity((int)e.MessageObject, false); - break; - case MessageType.RefreshByInstance: - var c3 = e.MessageObject as IContent; - if (c3 != null) - { - ReIndexForContent(c3, false); - } - break; - case MessageType.RemoveByInstance: - - // This is triggered when the item is permanently deleted - - var c4 = e.MessageObject as IContent; - if (c4 != null) - { - DeleteIndexForEntity(c4.Id, false); - } - break; - case MessageType.RefreshByJson: - - var jsonPayloads = UnpublishedPageCacheRefresher.DeserializeFromJsonPayload((string)e.MessageObject); - if (jsonPayloads.Any()) - { - foreach (var payload in jsonPayloads) - { - switch (payload.Operation) - { - case UnpublishedPageCacheRefresher.OperationType.Deleted: - - //permanently remove from all indexes - - DeleteIndexForEntity(payload.Id, false); - - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - } - - break; - - case MessageType.RefreshAll: - default: - //We don't support these, these message types will not fire for unpublished content - break; - } - } - - private static void ReIndexForMember(IMember member) - { - var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); - if (actions != null) - actions.Add(new DeferedReIndexForMember(member)); - else - DeferedReIndexForMember.Execute(member); - } - - /// - /// Event handler to create a lower cased version of the node name, this is so we can support case-insensitive searching and still - /// use the Whitespace Analyzer - /// - /// - /// - - private static void IndexerDocumentWriting(object sender, DocumentWritingEventArgs e) - { - if (e.Fields.Keys.Contains("nodeName")) - { - //TODO: This logic should really be put into the content indexer instead of hidden here!! - - //add the lower cased version - e.Document.Add(new Field("__nodeName", - e.Fields["nodeName"].ToLower(), - Field.Store.YES, - Field.Index.ANALYZED, - Field.TermVector.NO - )); - } - } - - private static void ReIndexForMedia(IMedia sender, bool isMediaPublished) - { - var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); - if (actions != null) - actions.Add(new DeferedReIndexForMedia(sender, isMediaPublished)); - else - DeferedReIndexForMedia.Execute(sender, isMediaPublished); - } - - /// - /// Remove items from any index that doesn't support unpublished content - /// - /// - /// - /// If true, indicates that we will only delete this item from indexes that don't support unpublished content. - /// If false it will delete this from all indexes regardless. - /// - private static void DeleteIndexForEntity(int entityId, bool keepIfUnpublished) - { - var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); - if (actions != null) - actions.Add(new DeferedDeleteIndex(entityId, keepIfUnpublished)); - else - DeferedDeleteIndex.Execute(entityId, keepIfUnpublished); - } - - /// - /// Re-indexes a content item whether published or not but only indexes them for indexes supporting unpublished content - /// - /// - /// - /// Value indicating whether the item is published or not - /// - private static void ReIndexForContent(IContent sender, bool isContentPublished) - { - var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); - if (actions != null) - actions.Add(new DeferedReIndexForContent(sender, isContentPublished)); - else - DeferedReIndexForContent.Execute(sender, isContentPublished); - } - - private class DeferedActions - { - private readonly List _actions = new List(); - - public static DeferedActions Get(IScopeProvider scopeProvider) - { - var scopeContext = scopeProvider.Context; - if (scopeContext == null) return null; - - return scopeContext.Enlist("examineEvents", - () => new DeferedActions(), // creator - (completed, actions) => // action - { - if (completed) actions.Execute(); - }, EnlistPriority); - } - - public void Add(DeferedAction action) - { - _actions.Add(action); - } - - private void Execute() - { - foreach (var action in _actions) - action.Execute(); - } - } - - private abstract class DeferedAction - { - public virtual void Execute() - { } - } - - private class DeferedReIndexForContent : DeferedAction - { - private readonly IContent _content; - private readonly bool _isPublished; - - public DeferedReIndexForContent(IContent content, bool isPublished) - { - _content = content; - _isPublished = isPublished; - } - - public override void Execute() - { - Execute(_content, _isPublished); - } - - public static void Execute(IContent content, bool isPublished) - { - var xml = content.ToXml(); - //add an icon attribute to get indexed - xml.Add(new XAttribute("icon", content.ContentType.Icon)); - - ExamineManager.Instance.ReIndexNode( - xml, IndexTypes.Content, - ExamineManager.Instance.IndexProviderCollection.OfType() - - //Index this item for all indexers if the content is published, otherwise if the item is not published - // then only index this for indexers supporting unpublished content - - .Where(x => isPublished || (x.SupportUnpublishedContent)) - .Where(x => x.EnableDefaultEventHandler)); - } - } - - private class DeferedReIndexForMedia : DeferedAction - { - private readonly IMedia _media; - private readonly bool _isPublished; - - public DeferedReIndexForMedia(IMedia media, bool isPublished) - { - _media = media; - _isPublished = isPublished; - } - - public override void Execute() - { - Execute(_media, _isPublished); - } - - public static void Execute(IMedia media, bool isPublished) - { - var xml = media.ToXml(); - //add an icon attribute to get indexed - xml.Add(new XAttribute("icon", media.ContentType.Icon)); - - ExamineManager.Instance.ReIndexNode( - xml, IndexTypes.Media, - ExamineManager.Instance.IndexProviderCollection.OfType() - - //Index this item for all indexers if the media is not trashed, otherwise if the item is trashed - // then only index this for indexers supporting unpublished media - - .Where(x => isPublished || (x.SupportUnpublishedContent)) - .Where(x => x.EnableDefaultEventHandler)); - } - } - - private class DeferedReIndexForMember : DeferedAction - { - private readonly IMember _member; - - public DeferedReIndexForMember(IMember member) - { - _member = member; - } - - public override void Execute() - { - Execute(_member); - } - - public static void Execute(IMember member) - { - ExamineManager.Instance.ReIndexNode( - member.ToXml(), IndexTypes.Member, - ExamineManager.Instance.IndexProviderCollection.OfType() - //ensure that only the providers are flagged to listen execute - .Where(x => x.EnableDefaultEventHandler)); - } - } - - private class DeferedDeleteIndex : DeferedAction - { - private readonly int _id; - private readonly bool _keepIfUnpublished; - - public DeferedDeleteIndex(int id, bool keepIfUnpublished) - { - _id = id; - _keepIfUnpublished = keepIfUnpublished; - } - - public override void Execute() - { - Execute(_id, _keepIfUnpublished); - } - - public static void Execute(int id, bool keepIfUnpublished) - { - ExamineManager.Instance.DeleteFromIndex( - id.ToString(CultureInfo.InvariantCulture), - ExamineManager.Instance.IndexProviderCollection.OfType() - - //if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, - // otherwise if keepIfUnpublished == false then remove from all indexes - - .Where(x => keepIfUnpublished == false || x.SupportUnpublishedContent == false) - .Where(x => x.EnableDefaultEventHandler)); - } - } - - /// - /// Converts a content node to XDocument - /// - /// - /// true if data is going to be returned from cache - /// - [Obsolete("This method is no longer used and will be removed from the core in future versions, the cacheOnly parameter has no effect. Use the other ToXDocument overload instead")] - public static XDocument ToXDocument(Content node, bool cacheOnly) - { - return ToXDocument(node); - } - - /// - /// Converts a content node to Xml - /// - /// - /// - private static XDocument ToXDocument(Content node) - { - if (TypeHelper.IsTypeAssignableFrom(node)) - { - return new XDocument(((Document) node).ContentEntity.ToXml()); - } - - if (TypeHelper.IsTypeAssignableFrom(node)) - { - return new XDocument(((global::umbraco.cms.businesslogic.media.Media) node).MediaItem.ToXml()); - } - - var xDoc = new XmlDocument(); - var xNode = xDoc.CreateNode(XmlNodeType.Element, "node", ""); - node.XmlPopulate(xDoc, ref xNode, false); - - if (xNode.Attributes["nodeTypeAlias"] == null) - { - //we'll add the nodeTypeAlias ourselves - XmlAttribute d = xDoc.CreateAttribute("nodeTypeAlias"); - d.Value = node.ContentType.Alias; - xNode.Attributes.Append(d); - } - - return new XDocument(ExamineXmlExtensions.ToXElement(xNode)); - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; +using System.Xml.Linq; +using Examine; +using Examine.LuceneEngine; +using Lucene.Net.Documents; +using Umbraco.Core; +using Umbraco.Core.Cache; +using Umbraco.Core.Logging; +using Umbraco.Core.Models; +using Umbraco.Core.Scoping; +using Umbraco.Core.Sync; +using Umbraco.Web.Cache; +using UmbracoExamine; +using Content = umbraco.cms.businesslogic.Content; +using Document = umbraco.cms.businesslogic.web.Document; + +namespace Umbraco.Web.Search +{ + /// + /// Used to wire up events for Examine + /// + public sealed class ExamineEvents : ApplicationEventHandler + { + // the default enlist priority is 100 + // enlist with a lower priority to ensure that anything "default" runs after us + // but greater that SafeXmlReaderWriter priority which is 60 + private const int EnlistPriority = 80; + + /// + /// Once the application has started we should bind to all events and initialize the providers. + /// + /// + /// + /// + /// We need to do this on the Started event as to guarantee that all resolvers are setup properly. + /// + protected override void ApplicationStarted(UmbracoApplicationBase httpApplication, ApplicationContext applicationContext) + { + LogHelper.Info("Initializing Examine and binding to business logic events"); + + var registeredProviders = ExamineManager.Instance.IndexProviderCollection + .OfType().Count(x => x.EnableDefaultEventHandler); + + LogHelper.Info("Adding examine event handlers for index providers: {0}", () => registeredProviders); + + //don't bind event handlers if we're not suppose to listen + if (registeredProviders == 0) + return; + + //Bind to distributed cache events - this ensures that this logic occurs on ALL servers that are taking part + // in a load balanced environment. + CacheRefresherBase.CacheUpdated += UnpublishedPageCacheRefresherCacheUpdated; + CacheRefresherBase.CacheUpdated += PublishedPageCacheRefresherCacheUpdated; + CacheRefresherBase.CacheUpdated += MediaCacheRefresherCacheUpdated; + CacheRefresherBase.CacheUpdated += MemberCacheRefresherCacheUpdated; + CacheRefresherBase.CacheUpdated += ContentTypeCacheRefresherCacheUpdated; + + var contentIndexer = ExamineManager.Instance.IndexProviderCollection[Constants.Examine.InternalIndexer] as UmbracoContentIndexer; + if (contentIndexer != null) + { + contentIndexer.DocumentWriting += IndexerDocumentWriting; + } + var memberIndexer = ExamineManager.Instance.IndexProviderCollection[Constants.Examine.InternalMemberIndexer] as UmbracoMemberIndexer; + if (memberIndexer != null) + { + memberIndexer.DocumentWriting += IndexerDocumentWriting; + } + } + + /// + /// This is used to refresh content indexers IndexData based on the DataService whenever a content type is changed since + /// properties may have been added/removed, then we need to re-index any required data if aliases have been changed + /// + /// + /// + /// + /// See: http://issues.umbraco.org/issue/U4-4798, http://issues.umbraco.org/issue/U4-7833 + /// + static void ContentTypeCacheRefresherCacheUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs e) + { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + + var indexersToUpdated = ExamineManager.Instance.IndexProviderCollection.OfType(); + foreach (var provider in indexersToUpdated) + { + provider.RefreshIndexerDataFromDataService(); + } + + if (e.MessageType == MessageType.RefreshByJson) + { + var contentTypesChanged = new HashSet(); + var mediaTypesChanged = new HashSet(); + var memberTypesChanged = new HashSet(); + + var payloads = ContentTypeCacheRefresher.DeserializeFromJsonPayload(e.MessageObject.ToString()); + foreach (var payload in payloads) + { + if (payload.IsNew == false + && (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved || payload.PropertyTypeAliasChanged)) + { + //if we get here it means that some aliases have changed and the indexes for those particular doc types will need to be updated + if (payload.Type == typeof(IContentType).Name) + { + //if it is content + contentTypesChanged.Add(payload.Alias); + } + else if (payload.Type == typeof(IMediaType).Name) + { + //if it is media + mediaTypesChanged.Add(payload.Alias); + } + else if (payload.Type == typeof(IMemberType).Name) + { + //if it is members + memberTypesChanged.Add(payload.Alias); + } + } + } + + //TODO: We need to update Examine to support re-indexing multiple items at once instead of one by one which will speed up + // the re-indexing process, we don't want to revert to rebuilding the whole thing! + + if (contentTypesChanged.Count > 0) + { + foreach (var alias in contentTypesChanged) + { + var ctType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias); + if (ctType != null) + { + var contentItems = ApplicationContext.Current.Services.ContentService.GetContentOfContentType(ctType.Id); + foreach (var contentItem in contentItems) + { + ReIndexForContent(contentItem, contentItem.HasPublishedVersion && contentItem.Trashed == false); + } + } + } + } + if (mediaTypesChanged.Count > 0) + { + foreach (var alias in mediaTypesChanged) + { + var ctType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias); + if (ctType != null) + { + var mediaItems = ApplicationContext.Current.Services.MediaService.GetMediaOfMediaType(ctType.Id); + foreach (var mediaItem in mediaItems) + { + ReIndexForMedia(mediaItem, mediaItem.Trashed == false); + } + } + } + } + if (memberTypesChanged.Count > 0) + { + foreach (var alias in memberTypesChanged) + { + var ctType = ApplicationContext.Current.Services.MemberTypeService.Get(alias); + if (ctType != null) + { + var memberItems = ApplicationContext.Current.Services.MemberService.GetMembersByMemberType(ctType.Id); + foreach (var memberItem in memberItems) + { + ReIndexForMember(memberItem); + } + } + } + } + } + + } + + static void MemberCacheRefresherCacheUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs e) + { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + + switch (e.MessageType) + { + case MessageType.RefreshById: + var c1 = ApplicationContext.Current.Services.MemberService.GetById((int)e.MessageObject); + if (c1 != null) + { + ReIndexForMember(c1); + } + break; + case MessageType.RemoveById: + + // This is triggered when the item is permanently deleted + + DeleteIndexForEntity((int)e.MessageObject, false); + break; + case MessageType.RefreshByInstance: + var c3 = e.MessageObject as IMember; + if (c3 != null) + { + ReIndexForMember(c3); + } + break; + case MessageType.RemoveByInstance: + + // This is triggered when the item is permanently deleted + + var c4 = e.MessageObject as IMember; + if (c4 != null) + { + DeleteIndexForEntity(c4.Id, false); + } + break; + case MessageType.RefreshAll: + case MessageType.RefreshByJson: + default: + //We don't support these, these message types will not fire for unpublished content + break; + } + } + + /// + /// Handles index management for all media events - basically handling saving/copying/trashing/deleting + /// + /// + /// + static void MediaCacheRefresherCacheUpdated(MediaCacheRefresher sender, CacheRefresherEventArgs e) + { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + + switch (e.MessageType) + { + case MessageType.RefreshById: + var c1 = ApplicationContext.Current.Services.MediaService.GetById((int)e.MessageObject); + if (c1 != null) + { + ReIndexForMedia(c1, c1.Trashed == false); + } + break; + case MessageType.RemoveById: + var c2 = ApplicationContext.Current.Services.MediaService.GetById((int)e.MessageObject); + if (c2 != null) + { + //This is triggered when the item has trashed. + // So we need to delete the index from all indexes not supporting unpublished content. + + DeleteIndexForEntity(c2.Id, true); + + //We then need to re-index this item for all indexes supporting unpublished content + + ReIndexForMedia(c2, false); + } + break; + case MessageType.RefreshByJson: + + var jsonPayloads = MediaCacheRefresher.DeserializeFromJsonPayload((string)e.MessageObject); + if (jsonPayloads.Any()) + { + foreach (var payload in jsonPayloads) + { + switch (payload.Operation) + { + case MediaCacheRefresher.OperationType.Saved: + var media1 = ApplicationContext.Current.Services.MediaService.GetById(payload.Id); + if (media1 != null) + { + ReIndexForMedia(media1, media1.Trashed == false); + } + break; + case MediaCacheRefresher.OperationType.Trashed: + + //keep if trashed for indexes supporting unpublished + //(delete the index from all indexes not supporting unpublished content) + + DeleteIndexForEntity(payload.Id, true); + + //We then need to re-index this item for all indexes supporting unpublished content + var media2 = ApplicationContext.Current.Services.MediaService.GetById(payload.Id); + if (media2 != null) + { + ReIndexForMedia(media2, false); + } + + break; + case MediaCacheRefresher.OperationType.Deleted: + + //permanently remove from all indexes + + DeleteIndexForEntity(payload.Id, false); + + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + break; + case MessageType.RefreshByInstance: + case MessageType.RemoveByInstance: + case MessageType.RefreshAll: + default: + //We don't support these, these message types will not fire for media + break; + } + } + + /// + /// Handles index management for all published content events - basically handling published/unpublished + /// + /// + /// + /// + /// This will execute on all servers taking part in load balancing + /// + static void PublishedPageCacheRefresherCacheUpdated(PageCacheRefresher sender, CacheRefresherEventArgs e) + { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + + switch (e.MessageType) + { + case MessageType.RefreshById: + var c1 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject); + if (c1 != null) + { + ReIndexForContent(c1, true); + } + break; + case MessageType.RemoveById: + + //This is triggered when the item has been unpublished or trashed (which also performs an unpublish). + + var c2 = ApplicationContext.Current.Services.ContentService.GetById((int)e.MessageObject); + if (c2 != null) + { + // So we need to delete the index from all indexes not supporting unpublished content. + + DeleteIndexForEntity(c2.Id, true); + + // We then need to re-index this item for all indexes supporting unpublished content + + ReIndexForContent(c2, false); + } + break; + case MessageType.RefreshByInstance: + var c3 = e.MessageObject as IContent; + if (c3 != null) + { + ReIndexForContent(c3, true); + } + break; + case MessageType.RemoveByInstance: + + //This is triggered when the item has been unpublished or trashed (which also performs an unpublish). + + var c4 = e.MessageObject as IContent; + if (c4 != null) + { + // So we need to delete the index from all indexes not supporting unpublished content. + + DeleteIndexForEntity(c4.Id, true); + + // We then need to re-index this item for all indexes supporting unpublished content + + ReIndexForContent(c4, false); + } + break; + case MessageType.RefreshAll: + case MessageType.RefreshByJson: + default: + //We don't support these for examine indexing + break; + } + } + + /// + /// Handles index management for all unpublished content events - basically handling saving/copying/deleting + /// + /// + /// + /// + /// This will execute on all servers taking part in load balancing + /// + static void UnpublishedPageCacheRefresherCacheUpdated(UnpublishedPageCacheRefresher sender, CacheRefresherEventArgs e) + { + if (Suspendable.ExamineEvents.CanIndex == false) + return; + + switch (e.MessageType) + { + case MessageType.RefreshById: + var c1 = ApplicationContext.Current.Services.ContentService.GetById((int) e.MessageObject); + if (c1 != null) + { + ReIndexForContent(c1, false); + } + break; + case MessageType.RemoveById: + + // This is triggered when the item is permanently deleted + + DeleteIndexForEntity((int)e.MessageObject, false); + break; + case MessageType.RefreshByInstance: + var c3 = e.MessageObject as IContent; + if (c3 != null) + { + ReIndexForContent(c3, false); + } + break; + case MessageType.RemoveByInstance: + + // This is triggered when the item is permanently deleted + + var c4 = e.MessageObject as IContent; + if (c4 != null) + { + DeleteIndexForEntity(c4.Id, false); + } + break; + case MessageType.RefreshByJson: + + var jsonPayloads = UnpublishedPageCacheRefresher.DeserializeFromJsonPayload((string)e.MessageObject); + if (jsonPayloads.Any()) + { + foreach (var payload in jsonPayloads) + { + switch (payload.Operation) + { + case UnpublishedPageCacheRefresher.OperationType.Deleted: + + //permanently remove from all indexes + + DeleteIndexForEntity(payload.Id, false); + + break; + case UnpublishedPageCacheRefresher.OperationType.Refresh:// RefreshNode or RefreshBranch (maybe trashed) + var c2 = ApplicationContext.Current.Services.ContentService.GetById(payload.Id); + if (c2 != null) + { + ReIndexForContent(c2, false); + } + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + } + + break; + + case MessageType.RefreshAll: + default: + //We don't support these, these message types will not fire for unpublished content + break; + } + } + + private static void ReIndexForMember(IMember member) + { + var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForMember(member)); + else + DeferedReIndexForMember.Execute(member); + } + + /// + /// Event handler to create a lower cased version of the node name, this is so we can support case-insensitive searching and still + /// use the Whitespace Analyzer + /// + /// + /// + + private static void IndexerDocumentWriting(object sender, DocumentWritingEventArgs e) + { + if (e.Fields.Keys.Contains("nodeName")) + { + //TODO: This logic should really be put into the content indexer instead of hidden here!! + + //add the lower cased version + e.Document.Add(new Field("__nodeName", + e.Fields["nodeName"].ToLower(), + Field.Store.YES, + Field.Index.ANALYZED, + Field.TermVector.NO + )); + } + } + + private static void ReIndexForMedia(IMedia sender, bool isMediaPublished) + { + var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForMedia(sender, isMediaPublished)); + else + DeferedReIndexForMedia.Execute(sender, isMediaPublished); + } + + /// + /// Remove items from any index that doesn't support unpublished content + /// + /// + /// + /// If true, indicates that we will only delete this item from indexes that don't support unpublished content. + /// If false it will delete this from all indexes regardless. + /// + private static void DeleteIndexForEntity(int entityId, bool keepIfUnpublished) + { + var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); + if (actions != null) + actions.Add(new DeferedDeleteIndex(entityId, keepIfUnpublished)); + else + DeferedDeleteIndex.Execute(entityId, keepIfUnpublished); + } + + /// + /// Re-indexes a content item whether published or not but only indexes them for indexes supporting unpublished content + /// + /// + /// + /// Value indicating whether the item is published or not + /// + private static void ReIndexForContent(IContent sender, bool isContentPublished) + { + var actions = DeferedActions.Get(ApplicationContext.Current.ScopeProvider); + if (actions != null) + actions.Add(new DeferedReIndexForContent(sender, isContentPublished)); + else + DeferedReIndexForContent.Execute(sender, isContentPublished); + } + + private class DeferedActions + { + private readonly List _actions = new List(); + + public static DeferedActions Get(IScopeProvider scopeProvider) + { + var scopeContext = scopeProvider.Context; + if (scopeContext == null) return null; + + return scopeContext.Enlist("examineEvents", + () => new DeferedActions(), // creator + (completed, actions) => // action + { + if (completed) actions.Execute(); + }, EnlistPriority); + } + + public void Add(DeferedAction action) + { + _actions.Add(action); + } + + private void Execute() + { + foreach (var action in _actions) + action.Execute(); + } + } + + private abstract class DeferedAction + { + public virtual void Execute() + { } + } + + private class DeferedReIndexForContent : DeferedAction + { + private readonly IContent _content; + private readonly bool _isPublished; + + public DeferedReIndexForContent(IContent content, bool isPublished) + { + _content = content; + _isPublished = isPublished; + } + + public override void Execute() + { + Execute(_content, _isPublished); + } + + public static void Execute(IContent content, bool isPublished) + { + var xml = content.ToXml(); + //add an icon attribute to get indexed + xml.Add(new XAttribute("icon", content.ContentType.Icon)); + + ExamineManager.Instance.ReIndexNode( + xml, IndexTypes.Content, + ExamineManager.Instance.IndexProviderCollection.OfType() + + //Index this item for all indexers if the content is published, otherwise if the item is not published + // then only index this for indexers supporting unpublished content + + .Where(x => isPublished || (x.SupportUnpublishedContent)) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedReIndexForMedia : DeferedAction + { + private readonly IMedia _media; + private readonly bool _isPublished; + + public DeferedReIndexForMedia(IMedia media, bool isPublished) + { + _media = media; + _isPublished = isPublished; + } + + public override void Execute() + { + Execute(_media, _isPublished); + } + + public static void Execute(IMedia media, bool isPublished) + { + var xml = media.ToXml(); + //add an icon attribute to get indexed + xml.Add(new XAttribute("icon", media.ContentType.Icon)); + + ExamineManager.Instance.ReIndexNode( + xml, IndexTypes.Media, + ExamineManager.Instance.IndexProviderCollection.OfType() + + //Index this item for all indexers if the media is not trashed, otherwise if the item is trashed + // then only index this for indexers supporting unpublished media + + .Where(x => isPublished || (x.SupportUnpublishedContent)) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedReIndexForMember : DeferedAction + { + private readonly IMember _member; + + public DeferedReIndexForMember(IMember member) + { + _member = member; + } + + public override void Execute() + { + Execute(_member); + } + + public static void Execute(IMember member) + { + ExamineManager.Instance.ReIndexNode( + member.ToXml(), IndexTypes.Member, + ExamineManager.Instance.IndexProviderCollection.OfType() + //ensure that only the providers are flagged to listen execute + .Where(x => x.EnableDefaultEventHandler)); + } + } + + private class DeferedDeleteIndex : DeferedAction + { + private readonly int _id; + private readonly bool _keepIfUnpublished; + + public DeferedDeleteIndex(int id, bool keepIfUnpublished) + { + _id = id; + _keepIfUnpublished = keepIfUnpublished; + } + + public override void Execute() + { + Execute(_id, _keepIfUnpublished); + } + + public static void Execute(int id, bool keepIfUnpublished) + { + ExamineManager.Instance.DeleteFromIndex( + id.ToString(CultureInfo.InvariantCulture), + ExamineManager.Instance.IndexProviderCollection.OfType() + + //if keepIfUnpublished == true then only delete this item from indexes not supporting unpublished content, + // otherwise if keepIfUnpublished == false then remove from all indexes + + .Where(x => keepIfUnpublished == false || x.SupportUnpublishedContent == false) + .Where(x => x.EnableDefaultEventHandler)); + } + } + + /// + /// Converts a content node to XDocument + /// + /// + /// true if data is going to be returned from cache + /// + [Obsolete("This method is no longer used and will be removed from the core in future versions, the cacheOnly parameter has no effect. Use the other ToXDocument overload instead")] + public static XDocument ToXDocument(Content node, bool cacheOnly) + { + return ToXDocument(node); + } + + /// + /// Converts a content node to Xml + /// + /// + /// + private static XDocument ToXDocument(Content node) + { + if (TypeHelper.IsTypeAssignableFrom(node)) + { + return new XDocument(((Document) node).ContentEntity.ToXml()); + } + + if (TypeHelper.IsTypeAssignableFrom(node)) + { + return new XDocument(((global::umbraco.cms.businesslogic.media.Media) node).MediaItem.ToXml()); + } + + var xDoc = new XmlDocument(); + var xNode = xDoc.CreateNode(XmlNodeType.Element, "node", ""); + node.XmlPopulate(xDoc, ref xNode, false); + + if (xNode.Attributes["nodeTypeAlias"] == null) + { + //we'll add the nodeTypeAlias ourselves + XmlAttribute d = xDoc.CreateAttribute("nodeTypeAlias"); + d.Value = node.ContentType.Alias; + xNode.Attributes.Append(d); + } + + return new XDocument(ExamineXmlExtensions.ToXElement(xNode)); + } + } +} From 377f1925eaa7add6b8c94bb29174da6873541d0e Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Fri, 31 Jan 2020 09:34:08 +0000 Subject: [PATCH 037/393] Obsolete CreatorName and WriterName on IPublishedContent --- .../PublishedContent/IPublishedContent.cs | 2 ++ .../PublishedContentHashtableConverter.cs | 2 ++ src/Umbraco.Web/Models/PublishedContentBase.cs | 4 ++++ .../PublishedCache/NuCache/PublishedContent.cs | 4 ++++ .../PublishedCache/PublishedMember.cs | 4 ++++ src/Umbraco.Web/PublishedContentExtensions.cs | 18 ++++++++++++++++-- 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 1c0d39a8b8..e8bee90d7c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -63,6 +63,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the name of the user who created the content item. /// + [Obsolete("Use CreatorName(IUserService) extension instead")] string CreatorName { get; } /// @@ -78,6 +79,7 @@ namespace Umbraco.Core.Models.PublishedContent /// /// Gets the name of the user who last updated the content item. /// + [Obsolete("Use WriterName(IUserService) extension instead")] string WriterName { get; } /// diff --git a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs index 41f0e2fb65..3bbc4a793b 100644 --- a/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs +++ b/src/Umbraco.Web/Macros/PublishedContentHashtableConverter.cs @@ -248,8 +248,10 @@ namespace Umbraco.Web.Macros public string UrlSegment => throw new NotImplementedException(); + [Obsolete("Use WriterName(IUserService) extension instead")] public string WriterName { get; } + [Obsolete("Use CreatorName(IUserService) extension instead")] public string CreatorName { get; } public int WriterId => _inner.WriterId; diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 148bab11c0..032a1a6fda 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -54,6 +54,8 @@ namespace Umbraco.Web.Models public abstract int CreatorId { get; } /// + [Obsolete("Use CreatorName(IUserService) extension instead")] + public abstract string CreatorName { get; } /// @@ -63,6 +65,8 @@ namespace Umbraco.Web.Models public abstract int WriterId { get; } /// + [Obsolete("Use WriterName(IUserService) extension instead")] + public abstract string WriterName { get; } /// diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index 3c3c3ac54f..c84d49d685 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -163,6 +163,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public override int CreatorId => _contentNode.CreatorId; /// + [Obsolete("Use CreatorName(IUserService) extension instead")] + public override string CreatorName => GetProfileNameById(_contentNode.CreatorId); /// @@ -172,6 +174,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public override int WriterId => ContentData.WriterId; /// + [Obsolete("Use WriterName(IUserService) extension instead")] + public override string WriterName => GetProfileNameById(ContentData.WriterId); /// diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 6e9ec61c62..30e5882ea8 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -140,9 +140,13 @@ namespace Umbraco.Web.PublishedCache public override string UrlSegment => throw new NotSupportedException(); // TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current + [Obsolete("Use WriterName(IUserService) extension instead")] + public override string WriterName => _member.GetCreatorProfile().Name; // TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current + [Obsolete("Use CreatorName(IUserService) extension instead")] + public override string CreatorName => _member.GetCreatorProfile().Name; public override int WriterId => _member.CreatorId; diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 061422859c..4e9735b0bd 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -28,6 +28,20 @@ namespace Umbraco.Web private static UmbracoContext UmbracoContext => Current.UmbracoContext; private static ISiteDomainHelper SiteDomainHelper => Current.Factory.GetInstance(); + #region Creator/Writer Names + + public static string CreatorName(this IPublishedContent content, IUserService userService) + { + return userService.GetProfileById(content.CreatorId)?.Name; + } + + public static string WriterName(this IPublishedContent content, IUserService userService) + { + return userService.GetProfileById(content.WriterId)?.Name; + } + + #endregion + #region IsComposedOf /// @@ -1004,8 +1018,8 @@ namespace Umbraco.Web { "NodeTypeAlias", n.ContentType.Alias }, { "CreateDate", n.CreateDate }, { "UpdateDate", n.UpdateDate }, - { "CreatorName", n.CreatorName }, - { "WriterName", n.WriterName }, + { "CreatorName", n.CreatorName(services.UserService) }, + { "WriterName", n.WriterName(services.UserService) }, { "Url", n.Url() } }; From b95d674092e8c71a8d135d8799746ffa6dfc29b8 Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Fri, 31 Jan 2020 09:50:37 +0000 Subject: [PATCH 038/393] Tidy up formatting --- src/Umbraco.Web/Models/PublishedContentBase.cs | 2 -- src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs | 2 -- src/Umbraco.Web/PublishedCache/PublishedMember.cs | 2 -- 3 files changed, 6 deletions(-) diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 032a1a6fda..4bdea8b920 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -55,7 +55,6 @@ namespace Umbraco.Web.Models /// [Obsolete("Use CreatorName(IUserService) extension instead")] - public abstract string CreatorName { get; } /// @@ -66,7 +65,6 @@ namespace Umbraco.Web.Models /// [Obsolete("Use WriterName(IUserService) extension instead")] - public abstract string WriterName { get; } /// diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs index c84d49d685..d80affbfa9 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedContent.cs @@ -164,7 +164,6 @@ namespace Umbraco.Web.PublishedCache.NuCache /// [Obsolete("Use CreatorName(IUserService) extension instead")] - public override string CreatorName => GetProfileNameById(_contentNode.CreatorId); /// @@ -175,7 +174,6 @@ namespace Umbraco.Web.PublishedCache.NuCache /// [Obsolete("Use WriterName(IUserService) extension instead")] - public override string WriterName => GetProfileNameById(ContentData.WriterId); /// diff --git a/src/Umbraco.Web/PublishedCache/PublishedMember.cs b/src/Umbraco.Web/PublishedCache/PublishedMember.cs index 30e5882ea8..e7cdb65f2e 100644 --- a/src/Umbraco.Web/PublishedCache/PublishedMember.cs +++ b/src/Umbraco.Web/PublishedCache/PublishedMember.cs @@ -141,12 +141,10 @@ namespace Umbraco.Web.PublishedCache // TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current [Obsolete("Use WriterName(IUserService) extension instead")] - public override string WriterName => _member.GetCreatorProfile().Name; // TODO: ARGH! need to fix this - this is not good because it uses ApplicationContext.Current [Obsolete("Use CreatorName(IUserService) extension instead")] - public override string CreatorName => _member.GetCreatorProfile().Name; public override int WriterId => _member.CreatorId; From 6b461671800c2224376d90ef201ad582ec05994c Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Fri, 31 Jan 2020 14:40:37 +0000 Subject: [PATCH 039/393] In GetDataTable use Obsolete CreatorName and WriterName for now --- src/Umbraco.Web/PublishedContentExtensions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index 4e9735b0bd..8d85c4615a 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -1018,8 +1018,8 @@ namespace Umbraco.Web { "NodeTypeAlias", n.ContentType.Alias }, { "CreateDate", n.CreateDate }, { "UpdateDate", n.UpdateDate }, - { "CreatorName", n.CreatorName(services.UserService) }, - { "WriterName", n.WriterName(services.UserService) }, + { "CreatorName", n.CreatorName }, + { "WriterName", n.WriterName }, { "Url", n.Url() } }; From a8672790d3d5b12717b909b2ef00569d47516253 Mon Sep 17 00:00:00 2001 From: Steve Temple Date: Fri, 31 Jan 2020 13:08:32 +0000 Subject: [PATCH 040/393] Obsolete Url on IPublishedContent --- src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs | 1 + .../Models/PublishedContent/PublishedContentWrapped.cs | 1 + src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs | 1 + src/Umbraco.Web/Controllers/UmbLoginController.cs | 2 +- src/Umbraco.Web/Models/PublishedContentBase.cs | 1 + src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs | 2 +- 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs index 1c0d39a8b8..3f8b1c2ff3 100644 --- a/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs +++ b/src/Umbraco.Core/Models/PublishedContent/IPublishedContent.cs @@ -97,6 +97,7 @@ namespace Umbraco.Core.Models.PublishedContent /// The value of this property is contextual. It depends on the 'current' request uri, /// if any. /// + [Obsolete("Use the Url() extension instead")] string Url { get; } /// diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs index fb41c95419..b83dc7a013 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentWrapped.cs @@ -94,6 +94,7 @@ namespace Umbraco.Core.Models.PublishedContent public virtual DateTime UpdateDate => _content.UpdateDate; /// + [Obsolete("Use the Url() extension instead")] public virtual string Url => _content.Url; /// diff --git a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs index 860b9b9179..a7b6d3d18a 100644 --- a/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs +++ b/src/Umbraco.Tests/PublishedContent/SolidPublishedSnapshot.cs @@ -199,6 +199,7 @@ namespace Umbraco.Tests.PublishedContent public DateTime UpdateDate { get; set; } public Guid Version { get; set; } public int Level { get; set; } + [Obsolete("Use the Url() extension instead")] public string Url { get; set; } public PublishedItemType ItemType => PublishedItemType.Content; diff --git a/src/Umbraco.Web/Controllers/UmbLoginController.cs b/src/Umbraco.Web/Controllers/UmbLoginController.cs index 2ff80e2668..88bc17abff 100644 --- a/src/Umbraco.Web/Controllers/UmbLoginController.cs +++ b/src/Umbraco.Web/Controllers/UmbLoginController.cs @@ -46,7 +46,7 @@ namespace Umbraco.Web.Controllers // if it's not a local url we'll redirect to the root of the current site return Redirect(Url.IsLocalUrl(model.RedirectUrl) ? model.RedirectUrl - : CurrentPage.AncestorOrSelf(1).Url); + : CurrentPage.AncestorOrSelf(1).Url()); } //redirect to current page by default diff --git a/src/Umbraco.Web/Models/PublishedContentBase.cs b/src/Umbraco.Web/Models/PublishedContentBase.cs index 148bab11c0..7f62c72004 100644 --- a/src/Umbraco.Web/Models/PublishedContentBase.cs +++ b/src/Umbraco.Web/Models/PublishedContentBase.cs @@ -69,6 +69,7 @@ namespace Umbraco.Web.Models public abstract DateTime UpdateDate { get; } /// + [Obsolete("Use the Url() extension instead")] public virtual string Url => this.Url(); /// diff --git a/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs b/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs index 0b2a607f8b..2a891ff1c7 100644 --- a/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs +++ b/src/Umbraco.Web/PropertyEditors/RichTextEditorPastedImages.cs @@ -101,7 +101,7 @@ namespace Umbraco.Web.PropertyEditors if (mediaTyped == null) throw new PanicException($"Could not find media by id {udi.Guid} or there was no UmbracoContext available."); - var location = mediaTyped.Url; + var location = mediaTyped.Url(); // Find the width & height attributes as we need to set the imageprocessor QueryString var width = img.GetAttributeValue("width", int.MinValue); From 889f0fc08553c5e6ef47c70b81181bfb3517bf88 Mon Sep 17 00:00:00 2001 From: elitsa Date: Mon, 3 Feb 2020 11:39:11 +0100 Subject: [PATCH 041/393] Html encoding document name when it's rendered in the relation types html. --- .../umbraco/developer/RelationTypes/EditRelationType.aspx.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs index 33366681f5..c718183988 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using umbraco.BasePages; @@ -105,9 +106,9 @@ namespace umbraco.cms.presentation.developer.RelationTypes readOnlyRelation.Id = reader.GetInt("id"); readOnlyRelation.ParentId = reader.GetInt("parentId"); - readOnlyRelation.ParentText = reader.GetString("parentText"); + readOnlyRelation.ParentText = HttpUtility.HtmlEncode(reader.GetString("parentText")); readOnlyRelation.ChildId = reader.GetInt("childId"); - readOnlyRelation.ChildText = reader.GetString("childText"); + readOnlyRelation.ChildText = HttpUtility.HtmlEncode(reader.GetString("childText")); readOnlyRelation.RelType = reader.GetInt("relType"); readOnlyRelation.DateTime = reader.GetDateTime("datetime"); readOnlyRelation.Comment = reader.GetString("comment"); From 3bfc2514301b14f03a6a9e6ac3c1cf515b838b84 Mon Sep 17 00:00:00 2001 From: elitsa Date: Mon, 3 Feb 2020 11:39:11 +0100 Subject: [PATCH 042/393] Html encoding document name when it's rendered in the relation types html. (cherry picked from commit 889f0fc08553c5e6ef47c70b81181bfb3517bf88) --- .../umbraco/developer/RelationTypes/EditRelationType.aspx.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs index 33366681f5..c718183988 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/RelationTypes/EditRelationType.aspx.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using umbraco.BasePages; @@ -105,9 +106,9 @@ namespace umbraco.cms.presentation.developer.RelationTypes readOnlyRelation.Id = reader.GetInt("id"); readOnlyRelation.ParentId = reader.GetInt("parentId"); - readOnlyRelation.ParentText = reader.GetString("parentText"); + readOnlyRelation.ParentText = HttpUtility.HtmlEncode(reader.GetString("parentText")); readOnlyRelation.ChildId = reader.GetInt("childId"); - readOnlyRelation.ChildText = reader.GetString("childText"); + readOnlyRelation.ChildText = HttpUtility.HtmlEncode(reader.GetString("childText")); readOnlyRelation.RelType = reader.GetInt("relType"); readOnlyRelation.DateTime = reader.GetDateTime("datetime"); readOnlyRelation.Comment = reader.GetString("comment"); From c5e0ef90c3f8aa88fb28bd289dd2358866ba7e07 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Mon, 10 Feb 2020 14:27:54 +0000 Subject: [PATCH 043/393] Verify & check SecurityStamp --- src/Umbraco.Web/Editors/AuthenticationController.cs | 7 ++++--- src/Umbraco.Web/Editors/BackOfficeController.cs | 12 +++++++++++- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index c2c481e8e4..c7c08d1b42 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -301,7 +301,7 @@ namespace Umbraco.Web.Editors if (user != null) { var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser.Id); - var callbackUrl = ConstructCallbackUrl(identityUser.Id, code); + var callbackUrl = ConstructCallbackUrl(identityUser.Id, code, identityUser.SecurityStamp.GenerateHash()); var message = Services.TextService.Localize("resetPasswordEmailCopyFormat", // Ensure the culture of the found user is used for the email! @@ -506,7 +506,7 @@ namespace Umbraco.Web.Editors return response; } - private string ConstructCallbackUrl(int userId, string code) + private string ConstructCallbackUrl(int userId, string code, string userSecurityStamp) { // Get an mvc helper to get the url var http = EnsureHttpContext(); @@ -516,7 +516,8 @@ namespace Umbraco.Web.Editors { area = GlobalSettings.GetUmbracoMvcArea(), u = userId, - r = code + r = code, + s = userSecurityStamp }); // Construct full URL using configured application URL (which will fall back to request) diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index e77a1b70f2..04c4627d85 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -296,11 +296,21 @@ namespace Umbraco.Web.Editors } [HttpGet] - public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode) + public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode, [Bind(Prefix = "s")]string stampHash) { var user = UserManager.FindById(userId); if (user != null) { + // Check security stamp that has been generated in forgotten password email link is the same we have stored for user + // ie the user has not been marked inactive or password changed by an admin etc + if(user.SecurityStamp.GenerateHash() != stampHash) + { + // Password, email or something changed to the user since the password reset email requested + // Add error and redirect for it to be displayed + TempData[ViewDataExtensions.TokenPasswordResetCode] = new[] { Services.TextService.Localize("login/resetCodeExpired") }; + return RedirectToLocal(Url.Action("Default", "BackOffice")); + } + var result = await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", resetCode, UserManager, user); if (result) { From 4a0ce8572c1b1e2fb1181bd0565a637fde19368c Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 10 Feb 2020 17:29:31 +0100 Subject: [PATCH 044/393] Nested Content can't add multiple items when configured with a single element type --- .../propertyeditors/nestedcontent/nestedcontent.controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 7de3a5b567..1c6d305c6b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -221,6 +221,7 @@ if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) { // only one scaffold type - no need to display the picker addNode(vm.scaffolds[0].contentTypeAlias); + vm.overlayMenu = null; return; } From c5ddba47a87ed5151dc94fc404b5c16c1d347917 Mon Sep 17 00:00:00 2001 From: emma burstow <37150989+emmaburstow@users.noreply.github.com> Date: Mon, 10 Feb 2020 16:21:03 +0000 Subject: [PATCH 045/393] Update SetUmbracoVersionStep.cs Punctuation fix --- src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index b5fdea32b7..17191d2521 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -13,7 +13,7 @@ using Umbraco.Web.Security; namespace Umbraco.Web.Install.InstallSteps { [InstallSetupStep(InstallationType.NewInstall | InstallationType.Upgrade, - "UmbracoVersion", 50, "Installation is complete!, get ready to be redirected to your new CMS.", + "UmbracoVersion", 50, "Installation is complete! Get ready to be redirected to your new CMS.", PerformsAppRestart = true)] internal class SetUmbracoVersionStep : InstallSetupStep { From 2c9c5baae51ef6b7ec86435892564fdb3aaa843e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Mon, 10 Feb 2020 13:59:11 +0100 Subject: [PATCH 046/393] Prevent multi URL picker from crashing with JS errors when absolute (non-local) links are added. --- .../propertyeditors/multiurlpicker/multiurlpicker.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js index 2e4313ec76..172f9b2249 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/multiurlpicker/multiurlpicker.controller.js @@ -147,7 +147,7 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en _.each($scope.model.value, function (item){ // we must reload the "document" link URLs to match the current editor culture - if (item.udi.indexOf("/document/") > 0) { + if (item.udi && item.udi.indexOf("/document/") > 0) { item.url = null; entityResource.getUrlByUdi(item.udi).then(function (data) { item.url = data; From 8e2ed68b887f78c20a740cf3c9684f2dd15ab988 Mon Sep 17 00:00:00 2001 From: Arkadiusz Biel Date: Tue, 11 Feb 2020 09:20:30 +0000 Subject: [PATCH 047/393] Add more descriptive exception (#7591) * Add more descriptive exepection * Finish with . * Update src/Umbraco.Core/Collections/TopoGraph.cs Co-Authored-By: Sebastiaan Janssen * Update src/Umbraco.Core/Collections/TopoGraph.cs Co-Authored-By: Sebastiaan Janssen Co-authored-by: Sebastiaan Janssen --- src/Umbraco.Core/Collections/TopoGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Collections/TopoGraph.cs b/src/Umbraco.Core/Collections/TopoGraph.cs index b8ded4a458..955a210465 100644 --- a/src/Umbraco.Core/Collections/TopoGraph.cs +++ b/src/Umbraco.Core/Collections/TopoGraph.cs @@ -126,7 +126,7 @@ namespace Umbraco.Core.Collections if (_items.TryGetValue(key, out value)) yield return value; else if (throwOnMissing) - throw new Exception(MissingDependencyError); + throw new Exception($"{MissingDependencyError} Error in type {typeof(TItem).Name}, with key {key}"); } } } From 6ae88f6c76373ca79939e28993572eb1c378d4f7 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 11 Feb 2020 09:56:30 +0000 Subject: [PATCH 048/393] Revert "Verify & check SecurityStamp" This reverts commit c5e0ef90c3f8aa88fb28bd289dd2358866ba7e07. --- src/Umbraco.Web/Editors/AuthenticationController.cs | 7 +++---- src/Umbraco.Web/Editors/BackOfficeController.cs | 12 +----------- 2 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web/Editors/AuthenticationController.cs b/src/Umbraco.Web/Editors/AuthenticationController.cs index c7c08d1b42..c2c481e8e4 100644 --- a/src/Umbraco.Web/Editors/AuthenticationController.cs +++ b/src/Umbraco.Web/Editors/AuthenticationController.cs @@ -301,7 +301,7 @@ namespace Umbraco.Web.Editors if (user != null) { var code = await UserManager.GeneratePasswordResetTokenAsync(identityUser.Id); - var callbackUrl = ConstructCallbackUrl(identityUser.Id, code, identityUser.SecurityStamp.GenerateHash()); + var callbackUrl = ConstructCallbackUrl(identityUser.Id, code); var message = Services.TextService.Localize("resetPasswordEmailCopyFormat", // Ensure the culture of the found user is used for the email! @@ -506,7 +506,7 @@ namespace Umbraco.Web.Editors return response; } - private string ConstructCallbackUrl(int userId, string code, string userSecurityStamp) + private string ConstructCallbackUrl(int userId, string code) { // Get an mvc helper to get the url var http = EnsureHttpContext(); @@ -516,8 +516,7 @@ namespace Umbraco.Web.Editors { area = GlobalSettings.GetUmbracoMvcArea(), u = userId, - r = code, - s = userSecurityStamp + r = code }); // Construct full URL using configured application URL (which will fall back to request) diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index 04c4627d85..e77a1b70f2 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -296,21 +296,11 @@ namespace Umbraco.Web.Editors } [HttpGet] - public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode, [Bind(Prefix = "s")]string stampHash) + public async Task ValidatePasswordResetCode([Bind(Prefix = "u")]int userId, [Bind(Prefix = "r")]string resetCode) { var user = UserManager.FindById(userId); if (user != null) { - // Check security stamp that has been generated in forgotten password email link is the same we have stored for user - // ie the user has not been marked inactive or password changed by an admin etc - if(user.SecurityStamp.GenerateHash() != stampHash) - { - // Password, email or something changed to the user since the password reset email requested - // Add error and redirect for it to be displayed - TempData[ViewDataExtensions.TokenPasswordResetCode] = new[] { Services.TextService.Localize("login/resetCodeExpired") }; - return RedirectToLocal(Url.Action("Default", "BackOffice")); - } - var result = await UserManager.UserTokenProvider.ValidateAsync("ResetPassword", resetCode, UserManager, user); if (result) { From a7ff82f29a9fb41cba693d023b391ef17ae1e25a Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Tue, 11 Feb 2020 11:52:44 +0000 Subject: [PATCH 049/393] Implements Shans notes on changins security stamp for chaning email/username --- .../Repositories/Implement/UserRepository.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 96abc37662..3be5102b83 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -557,6 +557,16 @@ ORDER BY colName"; } } + // If userlogin or the email has changed then need to reset security stamp + if (changedCols.Contains("userLogin") || changedCols.Contains("userEmail")) + { + userDto.EmailConfirmedDate = null; + userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString(); + + changedCols.Add("emailConfirmedDate"); + changedCols.Add("securityStampToken"); + } + //only update the changed cols if (changedCols.Count > 0) { From 4ff91ed4d1e4643998897dc6e347c132003839c9 Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 11 Feb 2020 20:15:22 +0100 Subject: [PATCH 050/393] Use button element in user detail and user group views (#7584) * Use button element on user detail page * Use button element on user group page --- .../src/views/users/group.html | 59 ++++++++----------- .../src/views/users/views/user/details.html | 32 +++++----- 2 files changed, 40 insertions(+), 51 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/users/group.html b/src/Umbraco.Web.UI.Client/src/views/users/group.html index 0244819655..8333528ddd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/group.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/group.html @@ -40,13 +40,12 @@ on-remove="vm.removeSelectedItem($index, vm.userGroup.sections)"> - + @@ -61,15 +60,13 @@ on-remove="vm.clearStartNode('content')"> - + @@ -85,14 +82,13 @@ on-remove="vm.clearStartNode('media')"> - + @@ -127,13 +123,12 @@ on-edit="vm.setPermissionsForNode(node)"> - + @@ -155,13 +150,11 @@ on-remove="vm.removeSelectedItem($index, vm.userGroup.users)"> - + diff --git a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html index e0cf09da50..bb3efaede2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/views/user/details.html @@ -75,13 +75,11 @@ on-remove="model.removeSelectedItem($index, model.user.userGroups)"> - + @@ -100,13 +98,12 @@ name="model.labels.noStartNodes"> - + @@ -125,13 +122,12 @@ name="model.labels.noStartNodes"> - + From b2141b665768b289e5fe2a1385f27cae12f6843e Mon Sep 17 00:00:00 2001 From: Steve Megson Date: Wed, 5 Feb 2020 15:00:46 +0000 Subject: [PATCH 051/393] Slider format function should return a number --- .../src/views/propertyeditors/slider/slider.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js index a20289d076..f5f0f7f2a2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/slider/slider.controller.js @@ -56,7 +56,7 @@ return value.toFixed(stepDecimalPlaces); }, from: function (value) { - return value; + return Number(value); } }, "range": { From 53ca5eec88694f5c062357717b428e429a91f11b Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Tue, 11 Feb 2020 20:36:42 +0100 Subject: [PATCH 052/393] Adjust child selector sorting (#7582) * Only add move cursor to sortable child elements * Don't sort add button * Add missing docs for onSort * Change add button to use button element * Use button element for remove button and add aria-hidden to icons * Reset styling of button element * Ensure add button is full width * Remove unnecessary cursor styles since these now inherit from button element --- .../components/umbchildselector.directive.js | 17 ++++++++----- .../less/components/umb-child-selector.less | 24 ++++++++++--------- .../views/components/umb-child-selector.html | 12 ++++++---- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js index a33fd4be53..96ce8735eb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js @@ -99,13 +99,18 @@ Use this directive to render a ui component for selecting child items to a paren @param {string} parentName (binding): The parent name. @param {string} parentIcon (binding): The parent icon. @param {number} parentId (binding): The parent id. -@param {callback} onRemove (binding): Callback when the remove button is clicked on an item. +@param {callback} onRemove (binding): Callback when removing an item.

    The callback returns:

    • child: The selected item.
    • $index: The selected item index.
    -@param {callback} onAdd (binding): Callback when the add button is clicked. +@param {callback} onAdd (binding): Callback when adding an item. +

    The callback returns:

    +
      +
    • $event: The select event.
    • +
    +@param {callback} onSort (binding): Callback when sorting an item.

    The callback returns:

    • $event: The select event.
    • @@ -174,16 +179,15 @@ Use this directive to render a ui component for selecting child items to a paren eventBindings.push(scope.$watch('parentName', function(newValue, oldValue){ if (newValue === oldValue) { return; } - if ( oldValue === undefined || newValue === undefined) { return; } + if (oldValue === undefined || newValue === undefined) { return; } syncParentName(); - })); eventBindings.push(scope.$watch('parentIcon', function(newValue, oldValue){ if (newValue === oldValue) { return; } - if ( oldValue === undefined || newValue === undefined) { return; } + if (oldValue === undefined || newValue === undefined) { return; } syncParentIcon(); })); @@ -191,6 +195,7 @@ Use this directive to render a ui component for selecting child items to a paren // sortable options for allowed child content types scope.sortableOptions = { axis: "y", + cancel: ".unsortable", containment: "parent", distance: 10, opacity: 0.7, @@ -199,7 +204,7 @@ Use this directive to render a ui component for selecting child items to a paren zIndex: 6000, update: function (e, ui) { if(scope.onSort) { - scope.onSort(); + scope.onSort(); } } }; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less index da690663d0..937f746c56 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less @@ -16,23 +16,24 @@ .umb-child-selector__child.-placeholder { border: 1px dashed @gray-8; background: none; - cursor: pointer; text-align: center; justify-content: center; - - color:@ui-action-type; + width: 100%; + color: @ui-action-type; + &:hover { - color:@ui-action-type-hover; - border-color:@ui-action-type-hover; - text-decoration:none; + color: @ui-action-type-hover; + border-color: @ui-action-type-hover; + text-decoration: none; } } .umb-child-selector__children-container { - margin-left: 30px; - .umb-child-selector__child { - cursor: move; - } + margin-left: 30px; + + .umb-child-selector__child.ui-sortable-handle { + cursor: move; + } } .umb-child-selector__child-description { @@ -65,5 +66,6 @@ } .umb-child-selector__child-remove { - cursor: pointer; + background: none; + border: none; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html index 0866e1bdf7..1d88c0eb96 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-child-selector.html @@ -19,18 +19,20 @@
      - +
      - {{ selectedChild.name }} + {{selectedChild.name}}
      - +
      - + From 32960219078bc599deb461ec26a1b008d726a8ad Mon Sep 17 00:00:00 2001 From: Mark Drake Date: Tue, 11 Feb 2020 15:10:28 -0500 Subject: [PATCH 053/393] Add an Active State for Expand (...) in the Top Section Navigation --- .../components/application/umbsections.directive.js | 11 +++++++++++ src/Umbraco.Web.UI.Client/src/less/sections.less | 9 ++++++++- .../views/components/application/umb-sections.html | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js index b8ee797c82..a33796ab6d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbsections.directive.js @@ -133,6 +133,17 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se } }; + scope.currentSectionInOverflow = function () { + if (scope.overflowingSections === 0) { + return false; + } + + var currentSection = scope.sections.filter(s => s.alias === scope.currentSection); + + return (scope.sections.indexOf(currentSection[0]) >= scope.maxSections); + + }; + loadSections(); } diff --git a/src/Umbraco.Web.UI.Client/src/less/sections.less b/src/Umbraco.Web.UI.Client/src/less/sections.less index 27b11b1c3b..1f19786b3c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/sections.less +++ b/src/Umbraco.Web.UI.Client/src/less/sections.less @@ -55,7 +55,7 @@ ul.sections { transition: opacity .1s linear, box-shadow .1s; } - &.current a { + &.current > a { color: @ui-active; &::after { @@ -76,6 +76,13 @@ ul.sections { transition: opacity .1s linear; } + &.current { + i { + opacity: 1; + background: @ui-active; + } + } + &:hover i { opacity: 1; } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html index 5688ba0e3b..0dac7176ee 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-sections.html @@ -11,7 +11,7 @@ -
    • +
    • From 2fc495128e21aaadf1fad80fd12859680998c492 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niels=20Lyngs=C3=B8?= Date: Wed, 29 Jan 2020 10:51:56 +0100 Subject: [PATCH 054/393] make icon of umb-preview-node align vertically. --- .../src/less/components/umb-node-preview.less | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less index f754a09368..939fd79826 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-node-preview.less @@ -29,7 +29,8 @@ .umb-node-preview__icon { display: flex; width: 25px; - height: 25px; + min-height: 25px; + height: 100%; justify-content: center; align-items: center; font-size: 20px; From e90e32b871b1d86269f47f7b60b159f8629a4773 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 12 Feb 2020 10:11:28 +0000 Subject: [PATCH 055/393] Add unit test to verify SecurityStamp changes/invalidates when the userlogin changes --- .../Repositories/UserRepositoryTest.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 3ba00e54cf..bbefb79f6b 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -421,6 +421,35 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Invalidate_SecurityStamp_On_Username_Change() + { + // Arrange + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider); + var userGroupRepository = CreateUserGroupRepository(provider); + + var user = CreateAndCommitUserWithGroup(repository, userGroupRepository); + var originalSecurityStamp = user.SecurityStamp; + + // Ensure when user generated a security stamp is present + Assert.That(user.SecurityStamp, Is.Not.Null); + Assert.That(user.SecurityStamp, Is.Not.Empty); + + // Update username + user.Username = user.Username + "UPDATED"; + repository.Save(user); + + // Get the user + var updatedUser = repository.Get(user.Id); + + // Ensure the Security Stamp is invalidated & no longer the same + Assert.AreNotEqual(originalSecurityStamp, updatedUser.SecurityStamp); + } + } + private void AssertPropertyValues(IUser updatedItem, IUser originalUser) { Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id)); From 9c428b7f0194197ab235c5799bb237d35b8b3a10 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 12 Feb 2020 11:55:51 +0000 Subject: [PATCH 056/393] Bump version to 8.5.4 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 841e986f15..fda69955d8 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -18,5 +18,5 @@ using System.Resources; [assembly: AssemblyVersion("8.0.0")] // these are FYI and changed automatically -[assembly: AssemblyFileVersion("8.5.3")] -[assembly: AssemblyInformationalVersion("8.5.3")] +[assembly: AssemblyFileVersion("8.5.4")] +[assembly: AssemblyInformationalVersion("8.5.4")] diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index e12eb8d19b..b465f89cb5 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -345,9 +345,9 @@ False True - 8530 + 8540 / - http://localhost:8530 + http://localhost:8540 False False @@ -429,4 +429,4 @@ - + \ No newline at end of file From 6a6978d8da08ba6f6e74c6b948cab35b01a0041a Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Wed, 12 Feb 2020 10:41:31 +0000 Subject: [PATCH 057/393] Merge pull request #7627 from umbraco/v8/bugfix/AB4828-resetpassword-mail AB4828 - Reset Password Email (cherry picked from commit f00680bfe69d09f9eda123b8218b2508d8f5ca3d) --- .../Repositories/Implement/UserRepository.cs | 10 +++++++ .../Repositories/UserRepositoryTest.cs | 29 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs index 96abc37662..3be5102b83 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/UserRepository.cs @@ -557,6 +557,16 @@ ORDER BY colName"; } } + // If userlogin or the email has changed then need to reset security stamp + if (changedCols.Contains("userLogin") || changedCols.Contains("userEmail")) + { + userDto.EmailConfirmedDate = null; + userDto.SecurityStampToken = entity.SecurityStamp = Guid.NewGuid().ToString(); + + changedCols.Add("emailConfirmedDate"); + changedCols.Add("securityStampToken"); + } + //only update the changed cols if (changedCols.Count > 0) { diff --git a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs index 3e5919d7f3..a5787d59e4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/UserRepositoryTest.cs @@ -409,6 +409,35 @@ namespace Umbraco.Tests.Persistence.Repositories } } + [Test] + public void Can_Invalidate_SecurityStamp_On_Username_Change() + { + // Arrange + var provider = TestObjects.GetScopeProvider(Logger); + using (var scope = provider.CreateScope()) + { + var repository = CreateRepository(provider); + var userGroupRepository = CreateUserGroupRepository(provider); + + var user = CreateAndCommitUserWithGroup(repository, userGroupRepository); + var originalSecurityStamp = user.SecurityStamp; + + // Ensure when user generated a security stamp is present + Assert.That(user.SecurityStamp, Is.Not.Null); + Assert.That(user.SecurityStamp, Is.Not.Empty); + + // Update username + user.Username = user.Username + "UPDATED"; + repository.Save(user); + + // Get the user + var updatedUser = repository.Get(user.Id); + + // Ensure the Security Stamp is invalidated & no longer the same + Assert.AreNotEqual(originalSecurityStamp, updatedUser.SecurityStamp); + } + } + private void AssertPropertyValues(IUser updatedItem, IUser originalUser) { Assert.That(updatedItem.Id, Is.EqualTo(originalUser.Id)); From a7ad464b40f3b3039f0137317cdc6a8170b181c0 Mon Sep 17 00:00:00 2001 From: abi Date: Sun, 2 Feb 2020 01:43:15 +0000 Subject: [PATCH 058/393] Remove Empty Statements --- src/Umbraco.Core/Cache/WebCachingAppCache.cs | 2 +- src/Umbraco.Core/HashGenerator.cs | 2 +- .../MergeDateAndDateTimePropertyEditor.cs | 2 +- .../Packaging/ConflictingPackageData.cs | 4 ++-- .../Packaging/PackagesRepository.cs | 11 ++++----- .../Repositories/Implement/MacroRepository.cs | 3 +-- .../Implement/TemplateRepository.cs | 1 - src/Umbraco.Core/Xml/XmlHelper.cs | 6 ++--- .../UmbracoTestDataController.cs | 14 +++++------ src/Umbraco.Tests/Logging/LogviewerTests.cs | 2 +- .../Repositories/MacroRepositoryTest.cs | 3 +-- .../Templates/Gallery.cshtml | 4 ++-- .../Partials/Grid/Bootstrap3-Fluid.cshtml | 14 +++++------ .../Views/Partials/Grid/Bootstrap3.cshtml | 4 ++-- src/Umbraco.Web/Cache/MacroCacheRefresher.cs | 2 +- src/Umbraco.Web/Editors/DataTypeController.cs | 2 +- .../Editors/PackageInstallController.cs | 4 ++-- .../Media/Exif/ExifBitConverter.cs | 2 +- .../Media/Exif/ExifExtendedProperty.cs | 24 +++++++++---------- .../Media/Exif/JFIFExtendedProperty.cs | 2 +- src/Umbraco.Web/Media/Exif/JPEGSection.cs | 2 +- src/Umbraco.Web/Media/Exif/MathEx.cs | 24 +++++++++---------- 22 files changed, 65 insertions(+), 69 deletions(-) diff --git a/src/Umbraco.Core/Cache/WebCachingAppCache.cs b/src/Umbraco.Core/Cache/WebCachingAppCache.cs index c6e104221a..044aab4c42 100644 --- a/src/Umbraco.Core/Cache/WebCachingAppCache.cs +++ b/src/Umbraco.Core/Cache/WebCachingAppCache.cs @@ -86,7 +86,7 @@ namespace Umbraco.Core.Cache protected override void EnterWriteLock() { - _locker.EnterWriteLock();; + _locker.EnterWriteLock(); } protected override void ExitReadLock() diff --git a/src/Umbraco.Core/HashGenerator.cs b/src/Umbraco.Core/HashGenerator.cs index 80cc3c3de4..58034f05d7 100644 --- a/src/Umbraco.Core/HashGenerator.cs +++ b/src/Umbraco.Core/HashGenerator.cs @@ -41,7 +41,7 @@ namespace Umbraco.Core internal void AddDateTime(DateTime d) { - _writer.Write(d.Ticks);; + _writer.Write(d.Ticks); } internal void AddString(string s) diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs index 0d451e8460..37a83bdd67 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/MergeDateAndDateTimePropertyEditor.cs @@ -32,7 +32,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 if (string.IsNullOrEmpty(dataType.Configuration)) { config.Format = "YYYY-MM-DD"; - }; + } } catch (Exception ex) { diff --git a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs index 82693677fb..33cb9c40d3 100644 --- a/src/Umbraco.Core/Packaging/ConflictingPackageData.cs +++ b/src/Umbraco.Core/Packaging/ConflictingPackageData.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Services; namespace Umbraco.Core.Packaging { - internal class ConflictingPackageData + internal class ConflictingPackageData { private readonly IMacroService _macroService; private readonly IFileService _fileService; @@ -23,7 +23,7 @@ namespace Umbraco.Core.Packaging return stylesheetNodes .Select(n => { - var xElement = n.Element("Name") ?? n.Element("name"); ; + var xElement = n.Element("Name") ?? n.Element("name"); if (xElement == null) throw new FormatException("Missing \"Name\" element"); diff --git a/src/Umbraco.Core/Packaging/PackagesRepository.cs b/src/Umbraco.Core/Packaging/PackagesRepository.cs index 72954b238d..5549b502fa 100644 --- a/src/Umbraco.Core/Packaging/PackagesRepository.cs +++ b/src/Umbraco.Core/Packaging/PackagesRepository.cs @@ -82,7 +82,7 @@ namespace Umbraco.Core.Packaging { var packagesXml = EnsureStorage(out _); if (packagesXml?.Root == null) - yield break;; + yield break; foreach (var packageXml in packagesXml.Root.Elements("package")) yield return _parser.ToPackageDefinition(packageXml); @@ -139,7 +139,7 @@ namespace Umbraco.Core.Packaging var updatedXml = _parser.ToXml(definition); packageXml.ReplaceWith(updatedXml); } - + packagesXml.Save(packagesFile); return true; @@ -212,7 +212,7 @@ namespace Umbraco.Core.Packaging compiledPackageXml.Save(packageXmlFileName); // check if there's a packages directory below media - + if (Directory.Exists(IOHelper.MapPath(_mediaFolderPath)) == false) Directory.CreateDirectory(IOHelper.MapPath(_mediaFolderPath)); @@ -510,7 +510,6 @@ namespace Umbraco.Core.Packaging private XElement GetStylesheetXml(string name, bool includeProperties) { if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value cannot be null or whitespace.", nameof(name)); -; var sts = _fileService.GetStylesheetByName(name); if (sts == null) return null; var stylesheetXml = new XElement("Stylesheet"); @@ -562,7 +561,7 @@ namespace Umbraco.Core.Packaging package.Add(new XElement("url", definition.Url)); var requirements = new XElement("requirements"); - + requirements.Add(new XElement("major", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Major.ToInvariantString() : definition.UmbracoVersion.Major.ToInvariantString())); requirements.Add(new XElement("minor", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Minor.ToInvariantString() : definition.UmbracoVersion.Minor.ToInvariantString())); requirements.Add(new XElement("patch", definition.UmbracoVersion == null ? UmbracoVersion.SemanticVersion.Patch.ToInvariantString() : definition.UmbracoVersion.Build.ToInvariantString())); @@ -589,7 +588,7 @@ namespace Umbraco.Core.Packaging contributors.Add(new XElement("contributor", contributor)); } } - + info.Add(contributors); info.Add(new XElement("readme", new XCData(definition.Readme))); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs index f0044e225d..1f0b45614b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MacroRepository.cs @@ -40,7 +40,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (macroDto == null) return null; - + var entity = MacroFactory.BuildEntity(macroDto); // reset dirty initial properties (U4-1946) @@ -153,7 +153,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistUpdatedItem(IMacro entity) { entity.UpdatingEntity(); -; var dto = MacroFactory.BuildDto(entity); Database.Update(dto); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs index b348317989..2175780916 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/TemplateRepository.cs @@ -215,7 +215,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement //Save updated entity to db template.UpdateDate = DateTime.Now; - ; var dto = TemplateFactory.BuildDto(template, NodeObjectTypeId, templateDto.PrimaryKey); Database.Update(dto.NodeDto); diff --git a/src/Umbraco.Core/Xml/XmlHelper.cs b/src/Umbraco.Core/Xml/XmlHelper.cs index 2dd955086d..d6461ec8c6 100644 --- a/src/Umbraco.Core/Xml/XmlHelper.cs +++ b/src/Umbraco.Core/Xml/XmlHelper.cs @@ -214,9 +214,9 @@ namespace Umbraco.Core.Xml var xmlDoc = new XmlDocument(); //Load the file into the XmlDocument xmlDoc.Load(reader); - + return xmlDoc; - } + } } /// @@ -335,7 +335,7 @@ namespace Umbraco.Core.Xml var child = parent.SelectSingleNode(name); if (child != null) { - child.InnerXml = ""; ; + child.InnerXml = ""; return child; } return AddCDataNode(xd, name, value); diff --git a/src/Umbraco.TestData/UmbracoTestDataController.cs b/src/Umbraco.TestData/UmbracoTestDataController.cs index 402c05cc1c..02949d5345 100644 --- a/src/Umbraco.TestData/UmbracoTestDataController.cs +++ b/src/Umbraco.TestData/UmbracoTestDataController.cs @@ -68,7 +68,7 @@ namespace Umbraco.TestData scope.Complete(); } - + return Content("Done"); } @@ -89,7 +89,7 @@ namespace Umbraco.TestData message = "Count not high enough for specified for number of levels required"; return false; } - + return true; } @@ -140,7 +140,7 @@ namespace Umbraco.TestData currChildCount = prev.childCount; // restore the parent parent = prev.parent; - + } else if (contentItem.Level < depth) { @@ -149,10 +149,10 @@ namespace Umbraco.TestData // not at max depth, create below parent = created.container(); - + currChildCount = 0; } - + } } @@ -219,7 +219,7 @@ namespace Umbraco.TestData { var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias); content.SetValue("review", faker.Rant.Review()); - content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); ; + content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]); Services.ContentService.Save(content); @@ -259,7 +259,7 @@ namespace Umbraco.TestData return docType; } - private IDataType GetOrCreateRichText() => GetOrCreateDataType(RichTextDataTypeName, Constants.PropertyEditors.Aliases.TinyMce); + private IDataType GetOrCreateRichText() => GetOrCreateDataType(RichTextDataTypeName, Constants.PropertyEditors.Aliases.TinyMce); private IDataType GetOrCreateMediaPicker() => GetOrCreateDataType(MediaPickerDataTypeName, Constants.PropertyEditors.Aliases.MediaPicker); diff --git a/src/Umbraco.Tests/Logging/LogviewerTests.cs b/src/Umbraco.Tests/Logging/LogviewerTests.cs index ed9deec177..08ba070b70 100644 --- a/src/Umbraco.Tests/Logging/LogviewerTests.cs +++ b/src/Umbraco.Tests/Logging/LogviewerTests.cs @@ -163,7 +163,7 @@ namespace Umbraco.Tests.Logging //Query @Level='Warning' BUT we pass in array of LogLevels for Debug & Info (Expect to get 0 results) string[] logLevelMismatch = { "Debug", "Information" }; - var filterLevelQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); ; + var filterLevelQuery = _logViewer.GetLogs(_logTimePeriod, pageNumber: 1, filterExpression: "@Level='Warning'", logLevels: logLevelMismatch); Assert.AreEqual(0, filterLevelQuery.TotalItems); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs index 6f215f4a35..9eca06d4e0 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MacroRepositoryTest.cs @@ -38,7 +38,6 @@ namespace Umbraco.Tests.Persistence.Repositories var repository = new MacroRepository((IScopeAccessor) provider, AppCaches.Disabled, Mock.Of()); var macro = new Macro("test1", "Test", "~/views/macropartials/test.cshtml", MacroTypes.PartialView); - ; Assert.Throws(() => repository.Save(macro)); } @@ -56,7 +55,7 @@ namespace Umbraco.Tests.Persistence.Repositories var macro = repository.Get(1); macro.Alias = "test2"; - + Assert.Throws(() => repository.Save(macro)); } diff --git a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml index 5aa09a3cb3..64be8eb60c 100755 --- a/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/PartialViewMacros/Templates/Gallery.cshtml @@ -29,13 +29,13 @@ @* a single image *@ if (media.IsDocumentType("Image")) { - @Render(media); + @Render(media) } @* a folder with images under it *@ foreach (var image in media.Children()) { - @Render(image); + @Render(image) } } diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml index defe59d808..131b0515ae 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3-Fluid.cshtml @@ -2,31 +2,31 @@ @using Umbraco.Web.Templates @using Newtonsoft.Json.Linq -@* +@* Razor helpers located at the bottom of this file *@ @if (Model != null && Model.sections != null) { var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1; - +
      @if (oneColumn) { foreach (var section in Model.sections) {
      @foreach (var row in section.rows) { - @renderRow(row); + @renderRow(row) }
      - } - }else { + } + }else {
      @foreach (var s in Model.sections) {
      @foreach (var row in s.rows) { - @renderRow(row); + @renderRow(row) }
      @@ -85,4 +85,4 @@ return new MvcHtmlString(string.Join(" ", attrs)); } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml index 9333628ed6..23fee33043 100644 --- a/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml +++ b/src/Umbraco.Web.UI/Views/Partials/Grid/Bootstrap3.cshtml @@ -12,7 +12,7 @@ foreach (var section in Model.sections) {
      @foreach (var row in section.rows) { - @renderRow(row, true); + @renderRow(row, true) }
      } @@ -23,7 +23,7 @@
      @foreach (var row in s.rows) { - @renderRow(row, false); + @renderRow(row, false) }
      diff --git a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs index 24479b8415..0cecba7b7b 100644 --- a/src/Umbraco.Web/Cache/MacroCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MacroCacheRefresher.cs @@ -53,7 +53,7 @@ namespace Umbraco.Web.Cache { macroRepoCache.Result.Clear(RepositoryCacheKeys.GetKey(payload.Id)); } - }; + } base.Refresh(json); } diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 5f5f5104cb..ad92d40ecf 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -427,7 +427,7 @@ namespace Umbraco.Web.Editors { var propertyEditor = propertyEditors.SingleOrDefault(x => x.Alias == dataType.Alias); if (propertyEditor != null) - dataType.HasPrevalues = propertyEditor.GetConfigurationEditor().Fields.Any(); ; + dataType.HasPrevalues = propertyEditor.GetConfigurationEditor().Fields.Any(); } var grouped = dataTypes diff --git a/src/Umbraco.Web/Editors/PackageInstallController.cs b/src/Umbraco.Web/Editors/PackageInstallController.cs index 6b2bd07fbd..1030498734 100644 --- a/src/Umbraco.Web/Editors/PackageInstallController.cs +++ b/src/Umbraco.Web/Editors/PackageInstallController.cs @@ -102,8 +102,8 @@ namespace Umbraco.Web.Editors model.LicenseUrl = ins.LicenseUrl; model.Readme = ins.Readme; model.ConflictingMacroAliases = ins.Warnings.ConflictingMacros.ToDictionary(x => x.Name, x => x.Alias); - model.ConflictingStyleSheetNames = ins.Warnings.ConflictingStylesheets.ToDictionary(x => x.Name, x => x.Alias); ; - model.ConflictingTemplateAliases = ins.Warnings.ConflictingTemplates.ToDictionary(x => x.Name, x => x.Alias); ; + model.ConflictingStyleSheetNames = ins.Warnings.ConflictingStylesheets.ToDictionary(x => x.Name, x => x.Alias); + model.ConflictingTemplateAliases = ins.Warnings.ConflictingTemplates.ToDictionary(x => x.Name, x => x.Alias); model.ContainsUnsecureFiles = ins.Warnings.UnsecureFiles.Any(); model.Url = ins.Url; model.Version = ins.Version; diff --git a/src/Umbraco.Web/Media/Exif/ExifBitConverter.cs b/src/Umbraco.Web/Media/Exif/ExifBitConverter.cs index 86bd12c15e..1dcae62acd 100644 --- a/src/Umbraco.Web/Media/Exif/ExifBitConverter.cs +++ b/src/Umbraco.Web/Media/Exif/ExifBitConverter.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Media.Exif public ExifBitConverter(ByteOrder from, ByteOrder to) : base(from, to) { - ; + } #endregion diff --git a/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs b/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs index 63c1ce3365..8889a13e1d 100644 --- a/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs +++ b/src/Umbraco.Web/Media/Exif/ExifExtendedProperty.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.Media.Exif public ExifEnumProperty(ExifTag tag, T value) : this(tag, value, false) { - ; + } public override ExifInterOperability Interoperability @@ -210,13 +210,13 @@ namespace Umbraco.Web.Media.Exif public ExifPointSubjectArea(ExifTag tag, ushort[] value) : base(tag, value) { - ; + } public ExifPointSubjectArea(ExifTag tag, ushort x, ushort y) - : base(tag, new ushort[] { x, y }) + : base(tag, new ushort[] {x, y}) { - ; + } } @@ -239,13 +239,13 @@ namespace Umbraco.Web.Media.Exif public ExifCircularSubjectArea(ExifTag tag, ushort[] value) : base(tag, value) { - ; + } public ExifCircularSubjectArea(ExifTag tag, ushort x, ushort y, ushort d) : base(tag, new ushort[] { x, y, d }) { - ; + } } @@ -269,13 +269,13 @@ namespace Umbraco.Web.Media.Exif public ExifRectangularSubjectArea(ExifTag tag, ushort[] value) : base(tag, value) { - ; + } public ExifRectangularSubjectArea(ExifTag tag, ushort x, ushort y, ushort w, ushort h) : base(tag, new ushort[] { x, y, w, h }) { - ; + } } @@ -303,13 +303,13 @@ namespace Umbraco.Web.Media.Exif public GPSLatitudeLongitude(ExifTag tag, MathEx.UFraction32[] value) : base(tag, value) { - ; + } public GPSLatitudeLongitude(ExifTag tag, float d, float m, float s) : base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(d), new MathEx.UFraction32(m), new MathEx.UFraction32(s) }) { - ; + } } @@ -331,13 +331,13 @@ namespace Umbraco.Web.Media.Exif public GPSTimeStamp(ExifTag tag, MathEx.UFraction32[] value) : base(tag, value) { - ; + } public GPSTimeStamp(ExifTag tag, float h, float m, float s) : base(tag, new MathEx.UFraction32[] { new MathEx.UFraction32(h), new MathEx.UFraction32(m), new MathEx.UFraction32(s) }) { - ; + } } diff --git a/src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs b/src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs index 94b255f4d1..24b3ac74be 100644 --- a/src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs +++ b/src/Umbraco.Web/Media/Exif/JFIFExtendedProperty.cs @@ -19,7 +19,7 @@ namespace Umbraco.Web.Media.Exif public JFIFVersion(ExifTag tag, ushort value) : base(tag, value) { - ; + } public override string ToString() diff --git a/src/Umbraco.Web/Media/Exif/JPEGSection.cs b/src/Umbraco.Web/Media/Exif/JPEGSection.cs index a1bc420fe4..78565d2bfa 100644 --- a/src/Umbraco.Web/Media/Exif/JPEGSection.cs +++ b/src/Umbraco.Web/Media/Exif/JPEGSection.cs @@ -45,7 +45,7 @@ public JPEGSection(JPEGMarker marker) : this(marker, new byte[0], new byte[0]) { - ; + } #endregion diff --git a/src/Umbraco.Web/Media/Exif/MathEx.cs b/src/Umbraco.Web/Media/Exif/MathEx.cs index 735358c40a..94cbccfbda 100644 --- a/src/Umbraco.Web/Media/Exif/MathEx.cs +++ b/src/Umbraco.Web/Media/Exif/MathEx.cs @@ -403,37 +403,37 @@ namespace Umbraco.Web.Media.Exif public Fraction32(int numerator, int denominator) : this(numerator, denominator, 0) { - ; + } public Fraction32(int numerator) : this(numerator, (int)1) { - ; + } public Fraction32(Fraction32 f) : this(f.Numerator, f.Denominator, f.Error) { - ; + } public Fraction32(float value) : this((double)value) { - ; + } public Fraction32(double value) : this(FromDouble(value)) { - ; + } public Fraction32(string s) : this(FromString(s)) { - ; + } #endregion @@ -1033,37 +1033,37 @@ namespace Umbraco.Web.Media.Exif public UFraction32(uint numerator, uint denominator) : this(numerator, denominator, 0) { - ; + } public UFraction32(uint numerator) : this(numerator, (uint)1) { - ; + } public UFraction32(UFraction32 f) : this(f.Numerator, f.Denominator, f.Error) { - ; + } public UFraction32(float value) : this((double)value) { - ; + } public UFraction32(double value) : this(FromDouble(value)) { - ; + } public UFraction32(string s) : this(FromString(s)) { - ; + } #endregion From 450a77fff56b17dca9e714ad6153bbb4778331ef Mon Sep 17 00:00:00 2001 From: Bjarne Fyrstenborg Date: Wed, 12 Feb 2020 15:21:03 +0100 Subject: [PATCH 059/393] Missing onClose for tour (#7595) --- .../components/application/umbtour/umbtourstep.directive.js | 6 +++--- .../src/views/components/application/umb-tour.html | 2 +- .../views/components/application/umbtour/umb-tour-step.html | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js index 2dc0ebdf93..381ecc76c3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbtour/umbtourstep.directive.js @@ -18,9 +18,9 @@ function link(scope, element, attrs, ctrl) { - scope.close = function() { - if(scope.onClose) { - scope.onClose(); + scope.close = function () { + if (scope.onClose) { + scope.onClose(); } } diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html index e358d75b9e..064dcf6945 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-tour.html @@ -70,7 +70,7 @@
      - +

      Oh, we got lost!

      diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umbtour/umb-tour-step.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umbtour/umb-tour-step.html index a2caff4ff2..9e161d6b4e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umbtour/umb-tour-step.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umbtour/umb-tour-step.html @@ -1,8 +1,7 @@
      -
      -
      /// For generic properties, the value is null. [DataMember] - internal Lazy PropertyGroupId + public Lazy PropertyGroupId { get => _propertyGroupId; set => SetPropertyValueAndDetectChanges(value, ref _propertyGroupId, nameof(PropertyGroupId)); From 5307a8cf13975a07f987b93b8a45d73eb88ea5df Mon Sep 17 00:00:00 2001 From: ksingercoffey <48767938+ksingercoffey@users.noreply.github.com> Date: Wed, 12 Feb 2020 22:41:19 -0800 Subject: [PATCH 061/393] #7643: add type="button" to prevent default clicks by pressing Enter (#7651) --- .../src/views/propertyeditors/mediapicker/mediapicker.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html index b17906272d..c4dba4d373 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/mediapicker/mediapicker.html @@ -36,16 +36,16 @@
      - -
    • -
    • From 69a1729967da863219326292da959d0686a7928e Mon Sep 17 00:00:00 2001 From: Paul Seal Date: Thu, 13 Feb 2020 08:07:46 +0000 Subject: [PATCH 062/393] Change the icon in the Document Types Tree to be the chosen icon instead of a default one. (#7358) * - changed the icon in the document types tree to be the icon chose rather than the default doc type icon * - rendered icons for media types and member types * - used null coalescing for icon setting for better null handling Co-authored-by: paulmoriyama <48755798+paulmoriyama@users.noreply.github.com> --- src/Umbraco.Web/Trees/ContentTypeTreeController.cs | 9 ++++++--- src/Umbraco.Web/Trees/MediaTypeTreeController.cs | 9 +++++++-- src/Umbraco.Web/Trees/MemberTypeTreeController.cs | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs index e8f8fb9f75..ece16229b2 100644 --- a/src/Umbraco.Web/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTypeTreeController.cs @@ -69,15 +69,18 @@ namespace Umbraco.Web.Trees .OrderBy(entity => entity.Name) .Select(dt => { + // get the content type here so we can get the icon from it to use when we create the tree node + // and we can enrich the result with content type data that's not available in the entity service output + var contentType = contentTypes[dt.Id]; + // since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. // need this check to keep supporting sites where children have already been created. var hasChildren = dt.HasChildren; - var node = CreateTreeNode(dt, Constants.ObjectTypes.DocumentType, id, queryStrings, Constants.Icons.ContentType, hasChildren); + var node = CreateTreeNode(dt, Constants.ObjectTypes.DocumentType, id, queryStrings, contentType?.Icon ?? Constants.Icons.ContentType, hasChildren); node.Path = dt.Path; - // enrich the result with content type data that's not available in the entity service output - var contentType = contentTypes[dt.Id]; + // now we can enrich the result with content type data that's not available in the entity service output node.Alias = contentType.Alias; node.AdditionalData["isElement"] = contentType.IsElement; diff --git a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs index 3d0046c319..7a9a80c8fc 100644 --- a/src/Umbraco.Web/Trees/MediaTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MediaTypeTreeController.cs @@ -25,10 +25,12 @@ namespace Umbraco.Web.Trees public class MediaTypeTreeController : TreeController, ISearchableTree { private readonly UmbracoTreeSearcher _treeSearcher; + private readonly IMediaTypeService _mediaTypeService; - public MediaTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) + public MediaTypeTreeController(UmbracoTreeSearcher treeSearcher, IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, IMediaTypeService mediaTypeService) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { _treeSearcher = treeSearcher; + _mediaTypeService = mediaTypeService; } protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings) @@ -54,6 +56,8 @@ namespace Umbraco.Web.Trees // if the request is for folders only then just return if (queryStrings["foldersonly"].IsNullOrWhiteSpace() == false && queryStrings["foldersonly"] == "1") return nodes; + var mediaTypes = _mediaTypeService.GetAll(); + nodes.AddRange( Services.EntityService.GetChildren(intId.Result, UmbracoObjectTypes.MediaType) .OrderBy(entity => entity.Name) @@ -62,7 +66,8 @@ namespace Umbraco.Web.Trees // since 7.4+ child type creation is enabled by a config option. It defaults to on, but can be disabled if we decide to. // need this check to keep supporting sites where children have already been created. var hasChildren = dt.HasChildren; - var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaType, id, queryStrings, Constants.Icons.MediaType, hasChildren); + var mt = mediaTypes.FirstOrDefault(x => x.Id == dt.Id); + var node = CreateTreeNode(dt, Constants.ObjectTypes.MediaType, id, queryStrings, mt?.Icon ?? Constants.Icons.MediaType, hasChildren); node.Path = dt.Path; return node; diff --git a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs index 2046baf2d3..5db9088f20 100644 --- a/src/Umbraco.Web/Trees/MemberTypeTreeController.cs +++ b/src/Umbraco.Web/Trees/MemberTypeTreeController.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Trees { return Services.MemberTypeService.GetAll() .OrderBy(x => x.Name) - .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, Constants.Icons.MemberType, false)); + .Select(dt => CreateTreeNode(dt, Constants.ObjectTypes.MemberType, id, queryStrings, dt?.Icon ?? Constants.Icons.MemberType, false)); } public IEnumerable Search(string query, int pageSize, long pageIndex, out long totalFound, string searchFrom = null) From 68ee537520cee054adbb5ca1151a7a7caf99aaab Mon Sep 17 00:00:00 2001 From: Poornima Nayar Date: Thu, 13 Feb 2020 08:10:06 +0000 Subject: [PATCH 063/393] Accessibility improvements to user group screen (#7414) --- .../components/users/umb-user-preview.html | 18 ++++++++---------- .../src/views/users/group.html | 3 +++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-preview.html b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-preview.html index 32aa5ed1a2..014297ae51 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-preview.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/users/umb-user-preview.html @@ -1,12 +1,11 @@
      - +
      @@ -15,7 +14,6 @@
      - Remove -
      - -
      \ No newline at end of file + +
      + diff --git a/src/Umbraco.Web.UI.Client/src/views/users/group.html b/src/Umbraco.Web.UI.Client/src/views/users/group.html index 8333528ddd..eae6dbd75c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/users/group.html +++ b/src/Umbraco.Web.UI.Client/src/views/users/group.html @@ -60,6 +60,7 @@ on-remove="vm.clearStartNode('content')"> + + @@ -82,6 +84,7 @@ on-remove="vm.clearStartNode('media')"> + -
    • -
    • From 5e1ac76e5f524ff6304f565ca78f5566952241fb Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 25 Feb 2020 21:11:17 +0100 Subject: [PATCH 108/393] Bugfix: Sometimes the httpcontext is null here --- src/Umbraco.Web/UmbracoContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index ded6182e17..a4f679ff5f 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -57,7 +57,7 @@ namespace Umbraco.Web // // all in all, this context may be disposed more than once, but DisposableObject ensures that // it is ok and it will be actually disposed only once. - httpContextAccessor.GetRequiredHttpContext().DisposeOnPipelineCompleted(this); + httpContextAccessor.HttpContext?.DisposeOnPipelineCompleted(this); ObjectCreated = DateTime.Now; UmbracoRequestId = Guid.NewGuid(); From f4cd1c2f0cdaada66b28c60c9a3dd8742dcd8a7e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 10 Jan 2020 14:28:37 +0100 Subject: [PATCH 109/393] Fix JS errors for Nested Content in single mode --- .../nestedcontent/nestedcontent.controller.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 875efa45f3..f2538c66c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -277,6 +277,9 @@ }; vm.getName = function (idx) { + if (!model.value || !model.value.length) { + return ""; + } var name = ""; @@ -326,6 +329,10 @@ }; vm.getIcon = function (idx) { + if (!model.value || !model.value.length) { + return ""; + } + var scaffold = getScaffold(model.value[idx].ncContentTypeAlias); return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder"; } From b48e288d717cf51dedfe80623fff27b5e5e36a43 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Fri, 10 Jan 2020 14:28:37 +0100 Subject: [PATCH 110/393] Fix JS errors for Nested Content in single mode (cherry picked from commit f4cd1c2f0cdaada66b28c60c9a3dd8742dcd8a7e) --- .../nestedcontent/nestedcontent.controller.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js index 1c6d305c6b..19547c38e4 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js @@ -277,6 +277,9 @@ }; vm.getName = function (idx) { + if (!model.value || !model.value.length) { + return ""; + } var name = ""; @@ -326,6 +329,10 @@ }; vm.getIcon = function (idx) { + if (!model.value || !model.value.length) { + return ""; + } + var scaffold = getScaffold(model.value[idx].ncContentTypeAlias); return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder"; } From 5e155a871cbb30f99cd7949330b6859f6a035f71 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 11 Feb 2020 07:42:54 +0100 Subject: [PATCH 111/393] Fix the create dialog on empty installs --- .../src/views/content/content.create.controller.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js index 64f601f8b7..1a1254fcf1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.create.controller.js @@ -22,10 +22,14 @@ function contentCreateController($scope, function initialize() { $scope.loading = true; $scope.allowedTypes = null; - $scope.countTypes = contentTypeResource.getCount; var getAllowedTypes = contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) { $scope.allowedTypes = iconHelper.formatContentTypeIcons(data); + if ($scope.allowedTypes.length === 0) { + contentTypeResource.getCount().then(function(count) { + $scope.countTypes = count; + }); + } }); var getCurrentUser = authResource.getCurrentUser().then(function (currentUser) { if (currentUser.allowedSections.indexOf("settings") > -1) { From e4edd47a484407724948f8e719aae95766b9263e Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 8 Feb 2020 20:24:02 +0100 Subject: [PATCH 112/393] Ensure that empty asset URLs in plugin manifests do not break the application --- src/Umbraco.Web/JavaScript/AssetInitialization.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web/JavaScript/AssetInitialization.cs b/src/Umbraco.Web/JavaScript/AssetInitialization.cs index 00605ece1f..42c750ffd3 100644 --- a/src/Umbraco.Web/JavaScript/AssetInitialization.cs +++ b/src/Umbraco.Web/JavaScript/AssetInitialization.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Web; using ClientDependency.Core; using ClientDependency.Core.Config; +using Umbraco.Core; using Umbraco.Web.Composing; using Umbraco.Web.PropertyEditors; @@ -35,7 +36,7 @@ namespace Umbraco.Web.JavaScript var requestUrl = httpContext.Request.Url; if (requestUrl == null) throw new ArgumentException("HttpContext.Request.Url is null.", nameof(httpContext)); - var dependencies = assets.Select(x => + var dependencies = assets.Where(x => x.IsNullOrWhiteSpace() == false).Select(x => { // most declarations with be made relative to the /umbraco folder, so things // like lib/blah/blah.js so we need to turn them into absolutes here From ea29eb993c8c77a49a76fd61fa9c24f5b7f3116b Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Sat, 8 Feb 2020 20:12:38 +0100 Subject: [PATCH 113/393] Align "Field is mandatory" label with the actual toggle --- .../propertysettings/propertysettings.controller.js | 4 +++- .../propertysettings/propertysettings.html | 11 ++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js index b8581d28d0..9640f2eba2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.controller.js @@ -56,7 +56,8 @@ "validation_validateAsEmail", "validation_validateAsNumber", "validation_validateAsUrl", - "validation_enterCustomValidation" + "validation_enterCustomValidation", + "validation_fieldIsMandatory" ]; localizationService.localizeMany(labels) @@ -66,6 +67,7 @@ vm.labels.validateAsNumber = data[1]; vm.labels.validateAsUrl = data[2]; vm.labels.customValidation = data[3]; + vm.labels.fieldIsMandatory = data[4]; vm.validationTypes = [ { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html index 4474390199..77ee276e3e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/propertysettings/propertysettings.html @@ -84,14 +84,15 @@
      - - + on-click="vm.toggleValidation()" + label-on="{{vm.labels.fieldIsMandatory}}" + label-off="{{vm.labels.fieldIsMandatory}}" + show-labels="true" + label-position="right" focus-when="{{vm.focusOnMandatoryField}}" + class="mb1"> Date: Sat, 8 Feb 2020 20:15:22 +0100 Subject: [PATCH 114/393] Only show culture for content links if there is more than one culture --- .../components/content/umbcontentnodeinfo.directive.js | 3 +++ .../src/views/components/content/umb-content-node-info.html | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 0ed1b12000..95b2a520d1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -328,6 +328,9 @@ // invariant nodes scope.currentUrls = scope.node.urls; } + + // figure out if multiple cultures apply across the content urls + scope.currentUrlsHaveMultipleCultures = _.keys(_.groupBy(scope.currentUrls, url => url.culture)).length > 1; } // load audit trail and redirects when on the info tab diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index c35686acd1..d8241b4dd7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -8,13 +8,13 @@