From a05cc03254684d2baf55e56315432b9f4458ef2c Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sun, 15 Nov 2015 21:18:21 +0100 Subject: [PATCH 001/406] Allows position of the list view child items tab to be positioned within the tab list --- .../listview/listview.controller.js | 1 + .../Mapping/TabsAndPropertiesResolver.cs | 39 +++++++++++++++---- .../PropertyEditors/ListViewPropertyEditor.cs | 12 ++---- 3 files changed, 36 insertions(+), 16 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 0569c9faca..069fd04988 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 @@ -57,6 +57,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific }; $scope.options = { + displayAtTabNumber: $scope.model.config.displayAtTabNumber ? $scope.model.config.displayAtTabNumber : 1, pageSize: $scope.model.config.pageSize ? $scope.model.config.pageSize : 10, pageNumber: ($routeParams.page && Number($routeParams.page) != NaN && Number($routeParams.page) > 0) ? $routeParams.page : 1, filter: '', diff --git a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs index ecf5e2da42..1856d4382e 100644 --- a/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/TabsAndPropertiesResolver.cs @@ -119,9 +119,6 @@ namespace Umbraco.Web.Models.Mapping //re-assign genericProps.Properties = contentProps; - - - } /// @@ -191,12 +188,38 @@ namespace Umbraco.Web.Models.Mapping }); listViewTab.Properties = listViewProperties; - //Is there a better way? - var tabs = new List>(); - tabs.Add(listViewTab); - tabs.AddRange(display.Tabs); - display.Tabs = tabs; + SetChildItemsTabPosition(display, listViewConfig, listViewTab); + } + private static void SetChildItemsTabPosition(TabbedContentItem display, + IDictionary listViewConfig, + Tab listViewTab) + where TPersisted : IContentBase + { + // Find position of tab from config + var tabIndexForChildItems = 0; + if (listViewConfig["displayAtTabNumber"] != null && int.TryParse((string)listViewConfig["displayAtTabNumber"], out tabIndexForChildItems)) + { + // Tab position is recorded 1-based but we insert into collection 0-based + tabIndexForChildItems--; + + // Ensure within bounds + if (tabIndexForChildItems < 0) + { + tabIndexForChildItems = 0; + } + + if (tabIndexForChildItems > display.Tabs.Count()) + { + tabIndexForChildItems = display.Tabs.Count(); + } + } + + // Recreate tab list with child items tab at configured position + var tabs = new List>(); + tabs.AddRange(display.Tabs); + tabs.Insert(tabIndexForChildItems, listViewTab); + display.Tabs = tabs; } protected override IEnumerable> ResolveCore(IContentBase content) diff --git a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs index 09dfacc94f..086004a501 100644 --- a/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/ListViewPropertyEditor.cs @@ -1,10 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; using Umbraco.Core; -using Umbraco.Core.Models; using Umbraco.Core.PropertyEditors; namespace Umbraco.Web.PropertyEditors @@ -24,6 +19,7 @@ namespace Umbraco.Web.PropertyEditors return new Dictionary { {"pageSize", "10"}, + {"displayAtTabNumber", "1"}, {"orderBy", "SortOrder"}, {"orderDirection", "asc"}, { @@ -40,6 +36,8 @@ namespace Umbraco.Web.PropertyEditors internal class ListViewPreValueEditor : PreValueEditor { + [PreValueField("displayAtTabNumber", "Display At Tab Number", "number", Description = "Which tab position that the list of child items will be displayed")] + public int DisplayAtTabNumber { get; set; } [PreValueField("pageSize", "Page Size", "number", Description = "Number of items per page")] public int PageSize { get; set; } @@ -55,7 +53,5 @@ namespace Umbraco.Web.PropertyEditors Description = "The properties that will be displayed for each column")] public object IncludeProperties { get; set; } } - - } } From cd3aaa47ca60954d4a48fe109631f67efebf6d9c Mon Sep 17 00:00:00 2001 From: bjarnef Date: Thu, 19 Nov 2015 00:25:05 +0100 Subject: [PATCH 002/406] Add retina version of Umbraco logo in login screen --- src/Umbraco.Web.UI.Client/src/less/login.less | 38 ++++++++++++++++--- 1 file changed, 33 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/login.less b/src/Umbraco.Web.UI.Client/src/less/login.less index 751ea8f3f0..a5ced443ff 100644 --- a/src/Umbraco.Web.UI.Client/src/less/login.less +++ b/src/Umbraco.Web.UI.Client/src/less/login.less @@ -9,13 +9,41 @@ color: @white; position: absolute; z-index: 10000; - top: 0px; - left: 0px; - margin: 0 !Important; + top: 0; + left: 0; + margin: 0 !important; padding: 0; border-radius: 0; } +/* 1.5 dpr */ +@media only screen and (-webkit-min-device-pixel-ratio: 1.5), + only screen and (min-resolution: 144dpi) +{ + .login-overlay { + background-image: url(../img/application/logo@2x.png) !important; + } +} + +/* 2.0 dpr */ +@media only screen and (-webkit-min-device-pixel-ratio: 2), + only screen and (min-resolution: 192dpi) +{ + .login-overlay { + background-image: url(../img/application/logo@2x.png) !important; + } +} + +/* 3.0 dpr - For iPhone 6 Plus, Samsung Galaxy S5 and similar devices */ +@media only screen and (-webkit-min-device-pixel-ratio: 3), + only screen and (min-resolution: 3dppx), /* default way */ + only screen and (min-resolution: 350dpi) /* dppx fallback */ +{ + .login-overlay { + background-image: url(../img/application/logo@3x.png) !important; + } +} + .login-overlay .umb-modalcolumn { background: none; border: none; @@ -62,7 +90,7 @@ } #hrOr hr { - margin: 0px; + margin: 0; border: none; background-color: @gray; height: 1px; @@ -79,4 +107,4 @@ height: 20px; margin: auto; color: @grayLight; -} +} \ No newline at end of file From 1cd00cb6f61b7e06ec6bb1527e081d52725bff5f Mon Sep 17 00:00:00 2001 From: Darren Ferguson Date: Fri, 20 Nov 2015 18:11:13 +0000 Subject: [PATCH 003/406] change the text "Reload nodes" to just "Reload" my editors don't know what a node is! --- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 2 +- src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index 582bb077ef..0a8411b04e 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -25,7 +25,7 @@ Public access Publish Unpublish - Reload nodes + Reload Republish entire site Restore Permissions diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index c9dcd1554a..df72aa8e0b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -25,7 +25,7 @@ Public access Publish Unpublish - Reload nodes + Reload Republish entire site Restore Permissions From 855e3b3dcce68be29732ac8dfe9c326b57895f68 Mon Sep 17 00:00:00 2001 From: engern Date: Sun, 29 Nov 2015 12:06:19 +0100 Subject: [PATCH 004/406] Add the possibility to sort the macro parameters --- .../umbraco/developer/Macros/editMacro.aspx | 11 +++++++++++ src/Umbraco.Web/Editors/MacroController.cs | 3 ++- .../Models/ContentEditing/MacroParameter.cs | 3 +++ .../umbraco/developer/Macros/editMacro.aspx.cs | 13 ++++++++----- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx b/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx index 2fef5e05bd..9d84f4ff50 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx @@ -137,6 +137,9 @@ <%=umbraco.ui.Text("general", "type",UmbracoUser)%> + + <%=umbraco.ui.Text("general", "sort",UmbracoUser)%> + @@ -161,6 +164,11 @@ DataTextFormatString="" DataTextField='Name' DataValueField="Alias"> + + Required
+ Numbers only
+ + @@ -184,6 +192,9 @@ DataSource='<%# GetMacroParameterEditors()%>'> + + <%-- The macro parameter will automatically get sort order when created. --%> + diff --git a/src/Umbraco.Web/Editors/MacroController.cs b/src/Umbraco.Web/Editors/MacroController.cs index 88af605f61..aed6cec602 100644 --- a/src/Umbraco.Web/Editors/MacroController.cs +++ b/src/Umbraco.Web/Editors/MacroController.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Text; @@ -34,7 +35,7 @@ namespace Umbraco.Web.Editors throw new HttpResponseException(HttpStatusCode.NotFound); } - return Mapper.Map>(macro); + return Mapper.Map>(macro).OrderBy(x => x.SortOrder); } /// diff --git a/src/Umbraco.Web/Models/ContentEditing/MacroParameter.cs b/src/Umbraco.Web/Models/ContentEditing/MacroParameter.cs index a5cf8733c3..2ca9d0fa90 100644 --- a/src/Umbraco.Web/Models/ContentEditing/MacroParameter.cs +++ b/src/Umbraco.Web/Models/ContentEditing/MacroParameter.cs @@ -20,6 +20,9 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "name")] public string Name { get; set; } + + [DataMember(Name = "sortOrder")] + public int SortOrder { get; set; } /// /// The editor view to render for this parameter diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs index f8d15004da..0c375893d6 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/developer/Macros/editMacro.aspx.cs @@ -193,7 +193,7 @@ namespace umbraco.cms.presentation.developer public void macroPropertyBind() { - macroProperties.DataSource = _macro.Properties; + macroProperties.DataSource = _macro.Properties.OrderBy(x => x.SortOrder); macroProperties.DataBind(); } @@ -333,24 +333,25 @@ namespace umbraco.cms.presentation.developer SetMacroValuesFromPostBack(_macro, Convert.ToInt32(tempCachePeriod), tempMacroAssembly, tempMacroType); // Save elements - var sort = 0; foreach (RepeaterItem item in macroProperties.Items) { var macroPropertyId = (HtmlInputHidden)item.FindControl("macroPropertyID"); var macroElementName = (TextBox)item.FindControl("macroPropertyName"); var macroElementAlias = (TextBox)item.FindControl("macroPropertyAlias"); + var macroElementSortOrder = (TextBox)item.FindControl("macroPropertySortOrder"); var macroElementType = (DropDownList)item.FindControl("macroPropertyType"); var prop = _macro.Properties.Single(x => x.Id == int.Parse(macroPropertyId.Value)); - + var sortOrder = 0; + int.TryParse(macroElementSortOrder.Text, out sortOrder); + _macro.Properties.UpdateProperty( prop.Alias, macroElementName.Text.Trim(), - sort, + sortOrder, macroElementType.SelectedValue, macroElementAlias.Text.Trim()); - sort++; } Services.MacroService.Save(_macro); @@ -368,6 +369,8 @@ namespace umbraco.cms.presentation.developer new LiteralControl("
")); + + macroPropertyBind(); } /// From 348694d9d41abb9e1095f2e3e68f2cfe66735870 Mon Sep 17 00:00:00 2001 From: engern Date: Sun, 29 Nov 2015 12:22:22 +0100 Subject: [PATCH 005/406] Use same color as the rest --- src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx b/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx index 9d84f4ff50..28cc136a3a 100644 --- a/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx +++ b/src/Umbraco.Web.UI/umbraco/developer/Macros/editMacro.aspx @@ -166,7 +166,7 @@ Required
- Numbers only
+ Numbers only
From dada91f006a21f4a8bc0f2682ff1f6be96b8ad2c Mon Sep 17 00:00:00 2001 From: kgiszewski Date: Mon, 14 Dec 2015 13:35:46 -0500 Subject: [PATCH 006/406] Fix u4-7534 --- .../src/common/services/contenteditinghelper.service.js | 2 +- .../src/common/services/formhelper.service.js | 2 +- .../src/views/content/content.edit.controller.js | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index ae4ca3cde8..7c245100ee 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -50,7 +50,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica var deferred = $q.defer(); - if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage })) { + if (!args.scope.busy && formHelper.submitForm({ scope: args.scope, statusMessage: args.statusMessage, action: args.action })) { args.scope.busy = true; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js index 057e0b8cff..289798ab52 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js @@ -50,7 +50,7 @@ function formHelper(angularHelper, serverValidationManager, $timeout, notificati } //the first thing any form must do is broadcast the formSubmitting event - args.scope.$broadcast("formSubmitting", { scope: args.scope }); + args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action }); //then check if the form is valid if (!args.skipValidation) { diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js index 03de8af21e..970bdff882 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/content/content.edit.controller.js @@ -74,7 +74,8 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ statusMessage: args.statusMessage, saveMethod: args.saveMethod, scope: $scope, - content: $scope.content + content: $scope.content, + action: args.action }).then(function (data) { //success init($scope.content); @@ -166,15 +167,15 @@ function ContentEditController($scope, $rootScope, $routeParams, $q, $timeout, $ }; $scope.sendToPublish = function () { - return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending..." }); + return performSave({ saveMethod: contentResource.sendToPublish, statusMessage: "Sending...", action: "sendToPublish" }); }; $scope.saveAndPublish = function () { - return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing..." }); + return performSave({ saveMethod: contentResource.publish, statusMessage: "Publishing...", action: "publish" }); }; $scope.save = function () { - return performSave({ saveMethod: contentResource.save, statusMessage: "Saving..." }); + return performSave({ saveMethod: contentResource.save, statusMessage: "Saving...", action: "save" }); }; $scope.preview = function (content) { From b52c480da35b591455f7521057267263dfa33a83 Mon Sep 17 00:00:00 2001 From: AndyButland Date: Sun, 20 Dec 2015 23:53:58 +0100 Subject: [PATCH 007/406] Added IPublishedContent extension method for filtering nodes based on composition alias Created unit test for IsComposedOf --- .../PublishedContent/PublishedContentType.cs | 12 ++++++------ .../PublishedContentMoreTests.cs | 6 +++--- .../PublishedContentTestElements.cs | 6 +++++- .../PublishedContent/PublishedContentTests.cs | 16 +++++++++++++--- src/Umbraco.Tests/Umbraco.Tests.csproj | 4 +++- src/Umbraco.Web/PublishedContentExtensions.cs | 15 +++++++++++++++ 6 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 5f30c08ce7..de54132e2c 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -1,9 +1,6 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Web.Caching; -using System.Web.UI; using Umbraco.Core.Cache; namespace Umbraco.Core.Models.PublishedContent @@ -27,6 +24,7 @@ namespace Umbraco.Core.Models.PublishedContent { Id = contentType.Id; Alias = contentType.Alias; + CompositionAliases = contentType.CompositionAliases(); _propertyTypes = contentType.CompositionPropertyTypes .Select(x => new PublishedPropertyType(this, x)) .ToArray(); @@ -34,10 +32,11 @@ namespace Umbraco.Core.Models.PublishedContent } // internal so it can be used for unit tests - internal PublishedContentType(int id, string alias, IEnumerable propertyTypes) + internal PublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) { Id = id; Alias = alias; + CompositionAliases = compositionAliases; _propertyTypes = propertyTypes.ToArray(); foreach (var propertyType in _propertyTypes) propertyType.ContentType = this; @@ -45,8 +44,8 @@ namespace Umbraco.Core.Models.PublishedContent } // create detached content type - ie does not match anything in the DB - internal PublishedContentType(string alias, IEnumerable propertyTypes) - : this (0, alias, propertyTypes) + internal PublishedContentType(string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) + : this(0, alias, compositionAliases, propertyTypes) { } private void InitializeIndexes() @@ -63,6 +62,7 @@ namespace Umbraco.Core.Models.PublishedContent public int Id { get; private set; } public string Alias { get; private set; } + public IEnumerable CompositionAliases { get; private set; } #endregion diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs index ff16c5306c..1c83691d04 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentMoreTests.cs @@ -234,9 +234,9 @@ namespace Umbraco.Tests.PublishedContent new PublishedPropertyType("prop1", 1, "?"), }; - var contentType1 = new PublishedContentType(1, "ContentType1", props); - var contentType2 = new PublishedContentType(2, "ContentType2", props); - var contentType2s = new PublishedContentType(3, "ContentType2Sub", props); + var contentType1 = new PublishedContentType(1, "ContentType1", Enumerable.Empty(), props); + var contentType2 = new PublishedContentType(2, "ContentType2", Enumerable.Empty(), props); + var contentType2s = new PublishedContentType(3, "ContentType2Sub", Enumerable.Empty(), props); cache.Add(new SolidPublishedContent(contentType1) { diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs index 4abfc6e18d..82115d670e 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTestElements.cs @@ -355,7 +355,11 @@ namespace Umbraco.Tests.PublishedContent private static readonly PublishedPropertyType Default = new PublishedPropertyType("*", 0, "?"); public AutoPublishedContentType(int id, string alias, IEnumerable propertyTypes) - : base(id, alias, propertyTypes) + : base(id, alias, Enumerable.Empty(), propertyTypes) + { } + + public AutoPublishedContentType(int id, string alias, IEnumerable compositionAliases, IEnumerable propertyTypes) + : base(id, alias, compositionAliases, propertyTypes) { } public override PublishedPropertyType GetPropertyType(string alias) diff --git a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs index 2aa72091de..3967afce79 100644 --- a/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs +++ b/src/Umbraco.Tests/PublishedContent/PublishedContentTests.cs @@ -50,7 +50,8 @@ namespace Umbraco.Tests.PublishedContent new PublishedPropertyType("content", 0, Constants.PropertyEditors.TinyMCEAlias), new PublishedPropertyType("testRecursive", 0, "?"), }; - var type = new AutoPublishedContentType(0, "anything", propertyTypes); + var compositionAliases = new[] {"MyCompositionAlias"}; + var type = new AutoPublishedContentType(0, "anything", compositionAliases, propertyTypes); PublishedContentType.GetPublishedContentTypeCallback = (alias) => type; } @@ -468,6 +469,16 @@ namespace Umbraco.Tests.PublishedContent Assert.IsNull(doc.FirstChild()); } + [Test] + public void IsComposedOf() + { + var doc = GetNode(1173); + + var isComposedOf = doc.IsComposedOf("MyCompositionAlias"); + + Assert.IsTrue(isComposedOf); + } + [Test] public void HasProperty() { @@ -475,8 +486,7 @@ namespace Umbraco.Tests.PublishedContent var hasProp = doc.HasProperty(Constants.Conventions.Content.UrlAlias); - Assert.AreEqual(true, (bool)hasProp); - + Assert.IsTrue(hasProp); } [Test] diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 59a9166e5a..1db382a004 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -410,7 +410,9 @@ - + + True + diff --git a/src/Umbraco.Web/PublishedContentExtensions.cs b/src/Umbraco.Web/PublishedContentExtensions.cs index d6cf3b3141..197175366e 100644 --- a/src/Umbraco.Web/PublishedContentExtensions.cs +++ b/src/Umbraco.Web/PublishedContentExtensions.cs @@ -116,6 +116,21 @@ namespace Umbraco.Web #endregion + #region IsComposedOf + + /// + /// Gets a value indicating whether the content is of a content type composed of the given alias + /// + /// The content. + /// The content type alias. + /// A value indicating whether the content is of a content type composed of a content type identified by the alias. + public static bool IsComposedOf(this IPublishedContent content, string alias) + { + return content.ContentType.CompositionAliases.Contains(alias); + } + + #endregion + #region HasProperty /// From ae85b7bf2ba00abe8f7375e24a90504a6d19e117 Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Mon, 28 Dec 2015 23:00:08 +0600 Subject: [PATCH 008/406] Nodes sort dialog - adding date formats Adding dates processing for 'dd.MM.yyyy' formats --- .../umbraco_client/Dialogs/SortDialog.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js b/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js index eb62afd805..dc74ad95ff 100644 --- a/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js +++ b/src/Umbraco.Web.UI/umbraco_client/Dialogs/SortDialog.js @@ -22,11 +22,11 @@ s = s.replace(/\-/g, "/"); //all of these basically transform the string into year-month-day since that //is what JS understands when creating a Date object - if (c.dateFormat.indexOf("dd/MM/yyyy") == 0 || c.dateFormat.indexOf("dd-MM-yyyy") == 0) { - s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3-$2-$1"); + if (c.dateFormat.indexOf("dd/MM/yyyy") == 0 || c.dateFormat.indexOf("dd-MM-yyyy") == 0 || c.dateFormat.indexOf("dd.MM.yyyy") == 0) { + s = s.replace(/(\d{1,2})[\/\-\.](\d{1,2})[\/\-\.](\d{4})/, "$3-$2-$1"); } - else if (c.dateFormat.indexOf("dd/MM/yy") == 0 || c.dateFormat.indexOf("dd-MM-yy") == 0) { - s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/, "$3-$2-$1"); + else if (c.dateFormat.indexOf("dd/MM/yy") == 0 || c.dateFormat.indexOf("dd-MM-yy") == 0 || c.dateFormat.indexOf("dd.MM.yy") == 0) { + s = s.replace(/(\d{1,2})[\/\-\.](\d{1,2})[\/\-\.](\d{2})/, "$3-$2-$1"); } else if (c.dateFormat.indexOf("MM/dd/yyyy") == 0 || c.dateFormat.indexOf("MM-dd-yyyy") == 0) { s = s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/, "$3-$1-$2"); @@ -118,4 +118,4 @@ -})(jQuery); \ No newline at end of file +})(jQuery); From aae15bf0b4b0695f1270e19f7a88fd2e7cfad0a3 Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Mon, 28 Dec 2015 23:40:15 +0600 Subject: [PATCH 009/406] ru.xml - UI lang file Added / updated keys for 7.4 --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index d5bfbaffb1..38e003bc23 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -386,6 +386,7 @@ Показать страницу при отправке Размер Сортировать + Отправить Тип Что искать? Вверх @@ -400,6 +401,8 @@ Добро пожаловать... Ширина Да + Пересортировать + Пересортировка завершена Цвет фона @@ -409,7 +412,8 @@ Текст - снизу и добавьте Ваш первый элемент]]> + Добавить содержимое + Сбросить содержимое Добавить шаблон сетки Настройте шаблон, задавая ширину колонок или добавляя дополнительные секции Добавить конфигурацию строки @@ -417,9 +421,12 @@ Добавить новые строки Доступны все редакторы Доступны все конфигурации строк + Choose a layout Кликните для встраивания Кликните для вставки изображения Колонки + Недопустимый тип содержимого + Данный тип содержимого разрешен Суммарное число колонок в шаблоне сетки Шаблоны сетки Шаблоны являются рабочим пространством для редактора сетки, обычно Вам понадобится не более одного или двух шаблонов @@ -430,6 +437,7 @@ Строки - это последовательности ячеек с горизонтальным расположением Установки будут сохранены только если они указаны в корректном формате json Установки + Установки применены Задайте установки, доступные редакторам для изменения Стили Задайте стили, доступные редакторам для изменения @@ -965,7 +973,7 @@ Обзор кэша Корзина Созданные пакеты - Типы данных + Типы данных Словарь Установленные пакеты Установить тему @@ -975,10 +983,10 @@ Макросы Типы медиа-материалов Участники - Группы участников + Группы участников Роли участников - Типы участников - Типы документов + Типы участников + Типы документов Пакеты дополнений Пакеты дополнений Скрипты Python From 4574e4291bf4a42c63f45adbdf8dbd79d640edf4 Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Mon, 28 Dec 2015 23:45:15 +0600 Subject: [PATCH 010/406] Missed keys --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index 38e003bc23..bb300dccf3 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -421,7 +421,7 @@ Добавить новые строки Доступны все редакторы Доступны все конфигурации строк - Choose a layout + Выберите раскладку Кликните для встраивания Кликните для вставки изображения Колонки From 6a1b8f00eb4c8d0a0c6b9f1be294a07888eeedb4 Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Mon, 28 Dec 2015 23:47:28 +0600 Subject: [PATCH 011/406] Correcing one key translation --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index bb300dccf3..a4bfc00cec 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -421,7 +421,7 @@ Добавить новые строки Доступны все редакторы Доступны все конфигурации строк - Выберите раскладку + Выберите шаблон Кликните для встраивания Кликните для вставки изображения Колонки From 698617dc60046c45f182586fc8d7d364a6ff3831 Mon Sep 17 00:00:00 2001 From: Chris Perks Date: Mon, 18 Jan 2016 14:41:35 +0000 Subject: [PATCH 012/406] Added mock for externalLoginInfo, and dependency references required to run 'dev' Grunt task --- .../src/common/mocks/services/externalLoginInfo.mocks.js | 8 ++++++++ src/Umbraco.Web.UI.Client/src/loader.dev.js | 4 ++++ 2 files changed, 12 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/common/mocks/services/externalLoginInfo.mocks.js diff --git a/src/Umbraco.Web.UI.Client/src/common/mocks/services/externalLoginInfo.mocks.js b/src/Umbraco.Web.UI.Client/src/common/mocks/services/externalLoginInfo.mocks.js new file mode 100644 index 0000000000..f054c8493e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/mocks/services/externalLoginInfo.mocks.js @@ -0,0 +1,8 @@ +angular.module("umbraco.mocks").factory('externalLoginInfo', + function () { + return { + errors: [], + providers: [] + }; + } +); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/loader.dev.js b/src/Umbraco.Web.UI.Client/src/loader.dev.js index 2e164f0eef..6fa09f00d9 100644 --- a/src/Umbraco.Web.UI.Client/src/loader.dev.js +++ b/src/Umbraco.Web.UI.Client/src/loader.dev.js @@ -12,6 +12,10 @@ LazyLoad.js( 'lib/angular/angular-ui-sortable.js', + 'lib/angular/1.1.5/angular-mocks.js', + 'lib/angular/1.1.5/angular-mobile.min.js', + 'lib/underscore/underscore-min.js', + 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', 'lib/bootstrap/js/bootstrap.2.3.2.min.js', From 9f365727e0e0d1c51e8f0c90cef8452986b87505 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 20 Jan 2016 14:10:31 +0100 Subject: [PATCH 013/406] Bumps version --- build/UmbracoVersion.txt | 2 +- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index e847b7606c..4f9245d744 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,2 +1,2 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) -7.3.5 \ No newline at end of file +7.3.6 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index f2c18b921b..e8cdf14c5b 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.3.5")] -[assembly: AssemblyInformationalVersion("7.3.5")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.3.6")] +[assembly: AssemblyInformationalVersion("7.3.6")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 8b9fbf05fd..4582e57e82 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration { public class UmbracoVersion { - private static readonly Version Version = new Version("7.3.5"); + private static readonly Version Version = new Version("7.3.6"); /// /// Gets the current version of Umbraco. diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 46ca224bb2..88b157f4f5 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2414,9 +2414,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\" True True - 7350 + 7360 / - http://localhost:7350 + http://localhost:7360 False False From 47ea3828a1526b01f8f418b7d5dafbe70fd3b98e Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Thu, 21 Jan 2016 13:36:37 +0600 Subject: [PATCH 014/406] Update UI language ru.xml According the latest keyas added/updated in 7.4 beta2 --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 188 ++++++++++++++++-- 1 file changed, 169 insertions(+), 19 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index e679003a25..782d56bb23 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -86,6 +86,7 @@ Сохранить Сохранить и опубликовать Сохранить и направить на публикацию + Сохранить список Выбрать Выбрать текущую папку Предварительный просмотр @@ -119,6 +120,14 @@ Вы не указали ни одного допустимого цвета + + Черный + Зеленый + Желтый + Оранжевый + Синий + Красный + Об этой странице Алиас @@ -138,6 +147,7 @@ Этот документ был изменен после публикации Этот документ не опубликован Документ опубликован + Здесь еще нет элементов. В этом списке пока нет элементов. Ссылка на медиа-элементы Тип медиа-контента @@ -169,12 +179,65 @@ Удалить файл Ссылка на документ + + Композиции + Вы не добавили ни одной вкладки + Добавить вкладку + Добавитьеще вкладку + Унаследован от + Добавить свойство + Обязательная метка + + Представление в формате списка + Устанавливает представление документа данного типа в виде сортируемого списка дочерних документов с функцией поиска, в отличие от обычного представления дочерних документов в виде дерева + + Допустимые шаблоны + Выберите перечень допустимых шаблонов для сопоставления документам данного типа + Разрешить в качестве корневого + Позволяет создавать документы этого типа в самом корне дерева содержимого + ДА - разрешить создание в качестве корневого + + Допустимые типы дочерних документов + Позволяет указать перечень типов документов, допустимых для создания документов, дочерних к данному типу + + Выбрать дочерний узел + Унаследовать вкладки и свойства из уже существующего типа документов. Вкладки будут либо добавлены в создаваемый тип, либо в случае совпадения названий вкладок будут добавлены наследуемые свойства. + Этот тип документов уже участвует в композиции другого типа, поэтому сам не может быть композицией. + В настоящее время нет типов документов, допустимых для построения композиции. + + Доступные редакторы + Переиспользовать + Установки редактора + + Конфигурирование + + ДА, удалить + + была перемещена внутрь + Выбрать папку для перемещения + в структуре дерева + + Все типы документов + Все документы + Все медиа-элементы + + , использующие этот тип документов, будут безвозвратно удалены, пожалуйста, подтвердите это действие. + , использующие этот тип медиа, будут безвозвратно удалены, пожалуйста, подтвердите это действие. + , использующие этот тип участников, будут безвозвратно удалены, пожалуйста, подтвердите это действие. + + и все документы, использующие данный тип + и все медиа-элементы, использующие данный тип + и все участники, использующие данный тип + + , использующие этот редактор, будут обновлены с применением этих установок + Где вы хотите создать новый %0% Создать в узле "Типы документов".]]> "Типы медиа-материалов".]]> Выберите тип и заголовок + Типу документа не сопоставлен шаблон Обзор сайта @@ -232,6 +295,40 @@ Кликните на изображении, чтобы увидеть полноразмерную версию Выберите элемент Просмотр элемента кэша + Создать папку... + + Связать с оригиналом + Самое дружелюбное сообщество + + Ссылка на страницу + + Открывает документ по ссылке в новом окне или вкладке браузера + Открывает документ по ссылке в полноэкранном режиме + Открывает документ по ссылке в родительском фрейме + + Ссылка на медиа-файл + + Выбрать медиа + Выбрать значок + Выбрать элемент + Выбрать ссылку + Выбрать макрос + Выбрать содержимое + Выбрать участника + Выбрать группу участников + + Это макрос без параметров + + Провайдеры аутентификации + Подробное сообщение об ошибке + Трассировка стека + + Связать + Разорвать связь + + учетную запись + + Выбрать редактор %0% - данные в некорректном формате + Получено сообщение об ошибке от сервера ПРЕДУПРЕЖДЕНИЕ! Несмотря на то, что CodeMirror по-умолчанию разрешен в данной конфигурации, он по-прежнему отключен для браузеров Internet Explorer ввиду нестабильной работы Укажите, пожалуйста, алиас и имя для этого свойства! Использование данного типа файлов на сайте запрещено администратором Ошибка доступа к указанному файлу или папке + Ошибка загрузки кода в частичном представлении (файл: %0%) + Ошибка загрузки пользовательского элемента управления '%0%' + Ошибка загрузки внешнего типа (сборка: %0%, тип: '%1%') + Ошибка загрузки макроса (файл: %0%) + "Ошибка разбора кода XSLT в файле: %0% + "Ошибка чтения XSLT-файла: %0% Ошибка в конфигурации типа данных, используемого для свойства, проверьте тип данных Укажите заголовок Выберите тип @@ -308,6 +412,7 @@ Действия Добавить Алиас + Все Вы уверены? Границы пользователем @@ -346,6 +451,7 @@ Внутренний отступ Вставить Установить + Неверно Выравнивание Язык Макет @@ -386,7 +492,7 @@ Показать страницу при отправке Размер Сортировать - Submit + Отправить Тип Что искать? Вверх @@ -401,8 +507,16 @@ Добро пожаловать... Ширина Да - Reorder - I am done reordering + Пересортировать + Пересортировка завершена + Предпросмотр + Сменить пароль + к + Список + Сохранение... + текущий + Переместить + Внедрить Цвет фона @@ -412,8 +526,8 @@ Текст - Add content - Drop content + Добавить содержимое + Сбросить содержимое Добавить шаблон сетки Настройте шаблон, задавая ширину колонок или добавляя дополнительные секции Добавить конфигурацию строки @@ -421,12 +535,12 @@ Добавить новые строки Доступны все редакторы Доступны все конфигурации строк - Choose a layout + Выберите шаблон Кликните для встраивания Кликните для вставки изображения Колонки - This content is not allowed here - This content is allowed here + Недопустимый тип содержимого + Данный тип содержимого разрешен Суммарное число колонок в шаблоне сетки Шаблоны сетки Шаблоны являются рабочим пространством для редактора сетки, обычно Вам понадобится не более одного или двух шаблонов @@ -437,10 +551,14 @@ Строки - это последовательности ячеек с горизонтальным расположением Установки будут сохранены только если они указаны в корректном формате json Установки - Settings applied + Установки применены Задайте установки, доступные редакторам для изменения Стили Задайте стили, доступные редакторам для изменения + Установить по-умолчанию + Выбрать дополнительно + Выбрать по-умолчанию + добавлены Страница @@ -479,22 +597,18 @@

Пожалуйста, не волнуйтесь, ни одной строки Вашей базы данных не будет потеряно при данной операции, и после ее завершения все будет работать!

]]> - - Нажмите кнопку Далее для продолжения.]]> + + Нажмите Далее для продолжения. ]]> + Далее для продолжения работы мастера настроек]]> Пароль пользователя по-умолчанию необходимо сменить!]]> Пользователь по-умолчанию заблокирован или не имеет доступа к Umbraco!

Не будет предпринято никаких дальнейших действий. Нажмите кнопку Далее для продолжения.]]> Пароль пользователя по-умолчанию успешно изменен в процессе установки!

Нет надобности в каких-либо дальнейших действиях. Нажмите кнопку Далее для продолжения.]]> Пароль изменен! Umbraco создает пользователя по-умолчанию - с именем входа ('admin') - и паролем ('default'). - ОЧЕНЬ ВАЖНО - изменить пароль по-умолчанию на что-либо уникальное.

-

Данный шаг произведет проверку пароля для пользователя по-умолчанию - и предложит сменить этот пароль в случае необходимости.

- ]]>
+

Umbraco создает пользователя по-умолчанию с именем входа ('admin') и паролем ('default'). + ОЧЕНЬ ВАЖНО изменить пароль по-умолчанию на что-либо уникальное.

]]> + Для начального обзора возможностей системы рекомендуем посмотреть ознакомительные видеоматериалы Далее (или модифицируя вручную ключ "UmbracoConfigurationStatus" в файле "web.config"), Вы принимаете лицензионное соглашение для данного программного обеспечения, расположенное ниже. Пожалуйста, обратите внимание, что инсталляционный пакет Umbraco отвечает двум различным типам лицензий: лицензии MIT на программные продукты с открытым исходным кодом в части ядра системы и свободной лицензии Umbraco в части пользовательского интерфейса.]]> Система не установлена. @@ -601,6 +715,10 @@ Нажмите, чтобы загрузить Перетащите файлы сюда... + Ссылка на файл + или нажмите сюда, чтобы выбрать файлы + Разрешены только типы файлов: + Максимально допустимый размер файла: Создать нового участника @@ -711,6 +829,8 @@ Пароль Что искать... Имя пользователя + Метка... + Укажите описание... Расширенный: Защита на основе ролей (групп) @@ -737,6 +857,9 @@ + @@ -790,6 +913,7 @@ Статистика Перевод Пользователи + Добавить значок в качестве родительского типа. Вкладки родительского типа не показаны и могут быть изменены непосредственно в родительском типе @@ -811,6 +935,24 @@ Заголовок вкладки Вкладки + + Добавить вкладку + Добавить свойство + Добавить редактор + Добавить шаблон + Добавить дочерний узел + Добавить дочерний + + Изменить тип данных + + Навигация по разделам + + Ярлыки + показать ярлыки + + В формате списка + Разрешить в качестве корневого + Сортировка завершена Перетаскивайте элементы на нужное место вверх или вниз для определения необходимого Вам порядка сортировки. Также можно щелкнуть по заголовкам столбцов, чтобы отсортировать все элементы сразу. @@ -1048,4 +1190,12 @@ Ваша недавняя история Ваш профиль + + Валидация + Валидация по формату email + Валидация числового значения + Валидация по формату Url + ...или указать свои правила валидации + Обязательно к заполению + From f78a42598552f7ee0c7dc6d545acf53bb7dacdb0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 16:37:47 +0100 Subject: [PATCH 015/406] U4-7816 The IsolatedCache instance in the RepositoryFactory differs from the ApplicationContext singleton instance --- .../Cache/IsolatedRuntimeCache.cs | 8 +- src/Umbraco.Core/CacheHelper.cs | 74 ++++++++----------- .../Persistence/RepositoryFactory.cs | 30 ++++---- src/Umbraco.Web/WebBootManager.cs | 15 +++- 4 files changed, 61 insertions(+), 66 deletions(-) diff --git a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs index 103f90345d..da20f7eb73 100644 --- a/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs +++ b/src/Umbraco.Core/Cache/IsolatedRuntimeCache.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Cache /// public class IsolatedRuntimeCache { - private readonly Func _cacheFactory; + internal Func CacheFactory { get; set; } /// /// Constructor that allows specifying a factory for the type of runtime isolated cache to create @@ -20,7 +20,7 @@ namespace Umbraco.Core.Cache /// public IsolatedRuntimeCache(Func cacheFactory) { - _cacheFactory = cacheFactory; + CacheFactory = cacheFactory; } private readonly ConcurrentDictionary _isolatedCache = new ConcurrentDictionary(); @@ -32,7 +32,7 @@ namespace Umbraco.Core.Cache /// public IRuntimeCacheProvider GetOrCreateCache() { - return _isolatedCache.GetOrAdd(typeof(T), type => _cacheFactory(type)); + return _isolatedCache.GetOrAdd(typeof(T), type => CacheFactory(type)); } /// @@ -41,7 +41,7 @@ namespace Umbraco.Core.Cache /// public IRuntimeCacheProvider GetOrCreateCache(Type type) { - return _isolatedCache.GetOrAdd(type, t => _cacheFactory(t)); + return _isolatedCache.GetOrAdd(type, t => CacheFactory(t)); } /// diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs index 303cf234fd..4b009e5f86 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/CacheHelper.cs @@ -18,12 +18,8 @@ namespace Umbraco.Core /// public class CacheHelper { - private readonly IsolatedRuntimeCache _isolatedCacheManager; - private readonly ICacheProvider _requestCache; private static readonly ICacheProvider NullRequestCache = new NullCacheProvider(); - private readonly ICacheProvider _staticCache; private static readonly ICacheProvider NullStaticCache = new NullCacheProvider(); - private readonly IRuntimeCacheProvider _runtimeCache; private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider(); /// @@ -90,45 +86,33 @@ namespace Umbraco.Core if (staticCacheProvider == null) throw new ArgumentNullException("staticCacheProvider"); if (requestCacheProvider == null) throw new ArgumentNullException("requestCacheProvider"); if (isolatedCacheManager == null) throw new ArgumentNullException("isolatedCacheManager"); - _runtimeCache = httpCacheProvider; - _staticCache = staticCacheProvider; - _requestCache = requestCacheProvider; - _isolatedCacheManager = isolatedCacheManager; + RuntimeCache = httpCacheProvider; + StaticCache = staticCacheProvider; + RequestCache = requestCacheProvider; + IsolatedRuntimeCache = isolatedCacheManager; } /// /// Returns the current Request cache /// - public ICacheProvider RequestCache - { - get { return _requestCache; } - } + public ICacheProvider RequestCache { get; internal set; } /// /// Returns the current Runtime cache /// - public ICacheProvider StaticCache - { - get { return _staticCache; } - } + public ICacheProvider StaticCache { get; internal set; } /// /// Returns the current Runtime cache /// - public IRuntimeCacheProvider RuntimeCache - { - get { return _runtimeCache; } - } + public IRuntimeCacheProvider RuntimeCache { get; internal set; } /// /// Returns the current Isolated Runtime cache manager /// - public IsolatedRuntimeCache IsolatedRuntimeCache - { - get { return _isolatedCacheManager; } - } - - #region Legacy Runtime/Http Cache accessors + public IsolatedRuntimeCache IsolatedRuntimeCache { get; internal set; } + + #region Legacy Runtime/Http Cache accessors /// /// Clears the item in umbraco's runtime cache @@ -137,8 +121,8 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearAllCache() { - _runtimeCache.ClearAllCache(); - _isolatedCacheManager.ClearAllCaches(); + RuntimeCache.ClearAllCache(); + IsolatedRuntimeCache.ClearAllCaches(); } /// @@ -149,7 +133,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheItem(string key) { - _runtimeCache.ClearCacheItem(key); + RuntimeCache.ClearCacheItem(key); } @@ -161,7 +145,7 @@ namespace Umbraco.Core [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] public void ClearCacheObjectTypes(string typeName) { - _runtimeCache.ClearCacheObjectTypes(typeName); + RuntimeCache.ClearCacheObjectTypes(typeName); } /// @@ -171,7 +155,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheObjectTypes() { - _runtimeCache.ClearCacheObjectTypes(); + RuntimeCache.ClearCacheObjectTypes(); } /// @@ -182,7 +166,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheByKeySearch(string keyStartsWith) { - _runtimeCache.ClearCacheByKeySearch(keyStartsWith); + RuntimeCache.ClearCacheByKeySearch(keyStartsWith); } /// @@ -193,14 +177,14 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public void ClearCacheByKeyExpression(string regexString) { - _runtimeCache.ClearCacheByKeyExpression(regexString); + RuntimeCache.ClearCacheByKeyExpression(regexString); } [Obsolete("Do not use this method, access the runtime cache from the RuntimeCache property")] [EditorBrowsable(EditorBrowsableState.Never)] public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { - return _runtimeCache.GetCacheItemsByKeySearch(keyStartsWith); + return RuntimeCache.GetCacheItemsByKeySearch(keyStartsWith); } /// @@ -213,7 +197,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey) { - return _runtimeCache.GetCacheItem(cacheKey); + return RuntimeCache.GetCacheItem(cacheKey); } /// @@ -227,7 +211,7 @@ namespace Umbraco.Core [EditorBrowsable(EditorBrowsableState.Never)] public TT GetCacheItem(string cacheKey, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem); } @@ -244,7 +228,7 @@ namespace Umbraco.Core public TT GetCacheItem(string cacheKey, TimeSpan timeout, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout); } @@ -263,7 +247,7 @@ namespace Umbraco.Core CacheItemRemovedCallback refreshAction, TimeSpan timeout, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, removedCallback: refreshAction); } @@ -283,7 +267,7 @@ namespace Umbraco.Core CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan timeout, Func getCacheItem) { - return _runtimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); + return RuntimeCache.GetCacheItem(cacheKey, getCacheItem, timeout, false, priority, refreshAction); } @@ -306,7 +290,7 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); @@ -330,7 +314,7 @@ namespace Umbraco.Core CacheDependency cacheDependency, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { var result = cache.GetCacheItem(cacheKey, () => getCacheItem(), null, false, priority, null, cacheDependency); @@ -352,7 +336,7 @@ namespace Umbraco.Core CacheItemPriority priority, Func getCacheItem) { - _runtimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); + RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, priority: priority); } @@ -371,7 +355,7 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - _runtimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); + RuntimeCache.InsertCacheItem(cacheKey, getCacheItem, timeout, priority: priority); } /// @@ -390,7 +374,7 @@ namespace Umbraco.Core TimeSpan timeout, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, null, cacheDependency); @@ -416,7 +400,7 @@ namespace Umbraco.Core TimeSpan? timeout, Func getCacheItem) { - var cache = _runtimeCache as HttpRuntimeCacheProvider; + var cache = RuntimeCache as HttpRuntimeCacheProvider; if (cache != null) { cache.InsertCacheItem(cacheKey, () => getCacheItem(), timeout, false, priority, refreshAction, cacheDependency); diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 9bb1d8d11c..1c1e551d34 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -31,29 +31,27 @@ namespace Umbraco.Core.Persistence //if (sqlSyntax == null) throw new ArgumentNullException("sqlSyntax"); if (settings == null) throw new ArgumentNullException("settings"); - _cacheHelper = cacheHelper; + _cacheHelper = cacheHelper; //IMPORTANT: We will force the DeepCloneRuntimeCacheProvider to be used here which is a wrapper for the underlying // runtime cache to ensure that anything that can be deep cloned in/out is done so, this also ensures that our tracks // changes entities are reset. if ((_cacheHelper.RuntimeCache is DeepCloneRuntimeCacheProvider) == false) { - var originalHelper = cacheHelper; - - _cacheHelper = new CacheHelper( - new DeepCloneRuntimeCacheProvider(originalHelper.RuntimeCache), - originalHelper.StaticCache, - originalHelper.RequestCache, - new IsolatedRuntimeCache(type => - { - var cache = originalHelper.IsolatedRuntimeCache.GetOrCreateCache(type); - return (cache is DeepCloneRuntimeCacheProvider) == false - //wrap the original if it's not DeepCloneRuntimeCacheProvider - ? new DeepCloneRuntimeCacheProvider(cache) - : cache; - })); + var origRuntimeCache = cacheHelper.RuntimeCache; + _cacheHelper.RuntimeCache = new DeepCloneRuntimeCacheProvider(origRuntimeCache); } - + //If the factory for isolated cache doesn't return DeepCloneRuntimeCacheProvider, then ensure it does + if (_cacheHelper.IsolatedRuntimeCache.CacheFactory.Method.ReturnType != typeof (DeepCloneRuntimeCacheProvider)) + { + var origFactory = cacheHelper.IsolatedRuntimeCache.CacheFactory; + _cacheHelper.IsolatedRuntimeCache.CacheFactory = type => + { + var cache = origFactory(type); + return new DeepCloneRuntimeCacheProvider(cache); + }; + } + _noCache = CacheHelper.CreateDisabledCacheHelper(); _logger = logger; _sqlSyntax = sqlSyntax; diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index f84d204bb1..dcf1e98be4 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -39,6 +39,7 @@ using Umbraco.Web.Scheduling; using Umbraco.Web.UI.JavaScript; using Umbraco.Web.WebApi; using umbraco.BusinessLogic; +using Umbraco.Core.Cache; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; @@ -240,7 +241,19 @@ namespace Umbraco.Web protected override CacheHelper CreateApplicationCache() { //create a web-based cache helper - return new CacheHelper(); + var cacheHelper = new CacheHelper( + //we need to have the dep clone runtime cache provider to ensure + //all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new HttpRuntimeCacheProvider(HttpRuntime.Cache)), + new StaticCacheProvider(), + //we have no request based cache when not running in web-based context + new NullCacheProvider(), + new IsolatedRuntimeCache(type => + //we need to have the dep clone runtime cache provider to ensure + //all entities are cached properly (cloned in and cloned out) + new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); + + return cacheHelper; } /// From 3ecb9d0f862347d515dd5e70890e14d77c34b952 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:05:27 +0100 Subject: [PATCH 016/406] U4-7807 Domain, language, public access cache (GetAll) caches not working when there are no items --- .../Cache/DefaultRepositoryCachePolicy.cs | 13 +++++- .../Cache/FullDataSetRepositoryCachePolicy.cs | 29 +++++++++++- ...FullDataSetRepositoryCachePolicyFactory.cs | 2 +- .../Cache/FullDataSetCachePolicyTests.cs | 45 ++++++++++++++++++- 4 files changed, 84 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 58b3a3956e..66d9b2ac25 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -158,8 +158,7 @@ namespace Umbraco.Core.Cache else if (_options.GetAllCacheAllowZeroCount) { //if the repository allows caching a zero count, then check the zero count cache - var zeroCount = Cache.GetCacheItem(GetCacheTypeKey()); - if (zeroCount != null && zeroCount.Any() == false) + if (HasZeroCountCache()) { //there is a zero count cache so return an empty list return new TEntity[] {}; @@ -179,6 +178,16 @@ namespace Umbraco.Core.Cache return entityCollection; } + /// + /// Looks up the zero count cache, must return null if it doesn't exist + /// + /// + protected virtual bool HasZeroCountCache() + { + var zeroCount = Cache.GetCacheItem(GetCacheTypeKey()); + return (zeroCount != null && zeroCount.Any() == false); + } + /// /// Performs the lookup for all entities of this type from the cache /// diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index c4c86b2ec7..40b100ef67 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Linq; using Umbraco.Core.Collections; using Umbraco.Core.Models.EntityBase; @@ -12,10 +14,18 @@ namespace Umbraco.Core.Cache internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IAggregateRoot { - public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, new RepositoryCachePolicyOptions()) + public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, + new RepositoryCachePolicyOptions + { + //Definitely allow zero'd cache entires since this is a full set, in many cases there will be none, + // and we must cache this! + GetAllCacheAllowZeroCount = true + }) { } + private bool? _hasZeroCountCache; + /// /// For this type of caching policy, we don't cache individual items /// @@ -44,6 +54,19 @@ namespace Umbraco.Core.Cache }); } + /// + /// Looks up the zero count cache, must return null if it doesn't exist + /// + /// + protected override bool HasZeroCountCache() + { + if (_hasZeroCountCache.HasValue) + return _hasZeroCountCache.Value; + + _hasZeroCountCache = Cache.GetCacheItem>(GetCacheTypeKey()) != null; + return _hasZeroCountCache.Value; + } + /// /// This policy will cache the full data set as a single collection /// @@ -51,6 +74,10 @@ namespace Umbraco.Core.Cache protected override TEntity[] GetAllFromCache() { var found = Cache.GetCacheItem>(GetCacheTypeKey()); + + //This method will get called before checking for zero count cache, so we'll just set the flag here + _hasZeroCountCache = found != null; + return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray(); } } diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs index 470db33b6a..75bdae7e83 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs @@ -14,7 +14,7 @@ namespace Umbraco.Core.Cache public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache) { - _runtimeCache = runtimeCache; + _runtimeCache = runtimeCache; } public virtual IRepositoryCachePolicy CreatePolicy() diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs index 7fd894e2a9..3d3884c686 100644 --- a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Web.Caching; using Moq; using NUnit.Framework; @@ -13,6 +14,48 @@ namespace Umbraco.Tests.Cache [TestFixture] public class FullDataSetCachePolicyTests { + [Test] + public void Get_All_Caches_Empty_List() + { + var cached = new List(); + + IList list = null; + + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback((string cacheKey, Func o, TimeSpan? t, bool b, CacheItemPriority cip, CacheItemRemovedCallback circ, string[] s) => + { + cached.Add(cacheKey); + + list = o() as IList; + }); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => + { + //return null if this is the first pass + return cached.Any() ? new DeepCloneableList() : null; + }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] {}, o => new AuditItem[] {}); + } + + Assert.AreEqual(1, cached.Count); + Assert.IsNotNull(list); + + //Do it again, ensure that its coming from the cache! + defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + using (defaultPolicy) + { + var found = defaultPolicy.GetAll(new object[] { }, o => new AuditItem[] { }); + } + + Assert.AreEqual(1, cached.Count); + Assert.IsNotNull(list); + } + [Test] public void Get_All_Caches_As_Single_List() { @@ -28,7 +71,7 @@ namespace Umbraco.Tests.Cache list = o() as IList; }); - cache.Setup(x => x.GetCacheItemsByKeySearch(It.IsAny())).Returns(new AuditItem[] { }); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem[] { }); var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); using (defaultPolicy) From fe774f557c279b59404b11a83975c92dc58de76d Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:06:14 +0100 Subject: [PATCH 017/406] Fixes ms value: U4-7810 DatabaseServerRegistrar should not execute as soon as a request is made, this slows down app start --- .../Sync/DatabaseServerRegistrarOptions.cs | 12 +- .../ServerRegistrationEventHandler.cs | 128 +++++++++++++----- 2 files changed, 106 insertions(+), 34 deletions(-) diff --git a/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs b/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs index 33ab1c8f57..5b5f6fc457 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerRegistrarOptions.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; namespace Umbraco.Core.Sync { @@ -14,13 +15,18 @@ namespace Umbraco.Core.Sync { StaleServerTimeout = TimeSpan.FromMinutes(2); // 2 minutes ThrottleSeconds = 30; // 30 seconds + RecurringSeconds = 60; // do it every minute } - /// - /// The number of seconds to wait between each updates to the database. - /// + [Obsolete("This is no longer used")] + [EditorBrowsable(EditorBrowsableState.Never)] public int ThrottleSeconds { get; set; } + /// + /// The amount of seconds to wait between calls to the database on the background thread + /// + public int RecurringSeconds { get; set; } + /// /// The time span to wait before considering a server stale, after it has last been accessed. /// diff --git a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs index 2a61d4177d..6c15814b92 100644 --- a/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs +++ b/src/Umbraco.Web/Strategies/ServerRegistrationEventHandler.cs @@ -1,4 +1,6 @@ using System; +using System.Threading; +using System.Threading.Tasks; using System.Web; using Newtonsoft.Json; using Umbraco.Core; @@ -6,6 +8,7 @@ using Umbraco.Core.Logging; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Web.Routing; +using Umbraco.Web.Scheduling; namespace Umbraco.Web.Strategies { @@ -22,74 +25,137 @@ namespace Umbraco.Web.Strategies /// public sealed class ServerRegistrationEventHandler : ApplicationEventHandler { - private readonly object _locko = new object(); private DatabaseServerRegistrar _registrar; - private DateTime _lastUpdated = DateTime.MinValue; + private BackgroundTaskRunner _backgroundTaskRunner; + private bool _started = false; // bind to events protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext) { _registrar = ServerRegistrarResolver.Current.Registrar as DatabaseServerRegistrar; + _backgroundTaskRunner = new BackgroundTaskRunner( + new BackgroundTaskRunnerOptions { AutoStart = true }, + applicationContext.ProfilingLogger.Logger); + // only for the DatabaseServerRegistrar if (_registrar == null) return; + //We will start the whole process when a successful request is made UmbracoModule.RouteAttempt += UmbracoModuleRouteAttempt; } - // handles route attempts. + /// + /// Handle when a request is made + /// + /// + /// + /// + /// We require this because: + /// - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest + /// - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest + /// we are safe, UmbracoApplicationUrl has been initialized + /// private void UmbracoModuleRouteAttempt(object sender, RoutableAttemptEventArgs e) { - if (e.HttpContext.Request == null || e.HttpContext.Request.Url == null) return; - switch (e.Outcome) { case EnsureRoutableOutcome.IsRoutable: // front-end request RegisterServer(e); + //remove handler, we're done + UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt; break; case EnsureRoutableOutcome.NotDocumentRequest: // anything else (back-end request, service...) //so it's not a document request, we'll check if it's a back office request if (e.HttpContext.Request.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath)) + { RegisterServer(e); - break; - /* - case EnsureRoutableOutcome.NotReady: - case EnsureRoutableOutcome.NotConfigured: - case EnsureRoutableOutcome.NoContent: - default: - // otherwise, do nothing - break; - */ + //remove handler, we're done + UmbracoModule.RouteAttempt -= UmbracoModuleRouteAttempt; + } + break; } } - - // register current server (throttled). + private void RegisterServer(UmbracoRequestEventArgs e) { - lock (_locko) // ensure we trigger only once - { - var secondsSinceLastUpdate = DateTime.Now.Subtract(_lastUpdated).TotalSeconds; - if (secondsSinceLastUpdate < _registrar.Options.ThrottleSeconds) return; - _lastUpdated = DateTime.Now; - } + //only process once + if (_started) return; + _started = true; + + var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl; var svc = e.UmbracoContext.Application.Services.ServerRegistrationService; - // because - // - ApplicationContext.UmbracoApplicationUrl is initialized by UmbracoModule in BeginRequest - // - RegisterServer is called on UmbracoModule.RouteAttempt which is triggered in ProcessRequest - // we are safe, UmbracoApplicationUrl has been initialized - var serverAddress = e.UmbracoContext.Application.UmbracoApplicationUrl; + //Perform the rest async, we don't want to block the startup sequence + // this will just reoccur on a background thread + _backgroundTaskRunner.Add(new TouchServerTask(_backgroundTaskRunner, + 15000, //delay before first execution + _registrar.Options.RecurringSeconds * 1000, //amount of ms between executions + svc, _registrar, serverAddress)); + } - try + private class TouchServerTask : RecurringTaskBase + { + private readonly IServerRegistrationService _svc; + private readonly DatabaseServerRegistrar _registrar; + private readonly string _serverAddress; + + /// + /// Initializes a new instance of the class. + /// + /// The task runner. + /// The delay. + /// The period. + /// + /// + /// + /// The task will repeat itself periodically. Use this constructor to create a new task. + public TouchServerTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds, + IServerRegistrationService svc, DatabaseServerRegistrar registrar, string serverAddress) + : base(runner, delayMilliseconds, periodMilliseconds) { - svc.TouchServer(serverAddress, svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); + if (svc == null) throw new ArgumentNullException("svc"); + _svc = svc; + _registrar = registrar; + _serverAddress = serverAddress; } - catch (Exception ex) + + public override bool IsAsync { - LogHelper.Error("Failed to update server record in database.", ex); + get { return false; } + } + + public override bool RunsOnShutdown + { + get { return false; } + } + + /// + /// Runs the background task. + /// + /// A value indicating whether to repeat the task. + public override bool PerformRun() + { + try + { + _svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout); + + return true; // repeat + } + catch (Exception ex) + { + LogHelper.Error("Failed to update server record in database.", ex); + + return false; // probably stop if we have an error + } + } + + public override Task PerformRunAsync(CancellationToken token) + { + throw new NotImplementedException(); } } } From 4dc4c36581379b3ff5f2d18e6609e32fecdd2183 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:08:21 +0100 Subject: [PATCH 018/406] U4-7811 Content type repositories should use FullDataSetRepositoryCachePolicyFactory for performance Conflicts: src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs --- .../Repositories/ContentTypeBaseRepository.cs | 93 +++++-------------- .../Repositories/ContentTypeRepository.cs | 47 +++++++--- .../IContentTypeCompositionRepository.cs | 11 +++ .../Interfaces/IContentTypeRepository.cs | 2 +- .../Interfaces/IMediaTypeRepository.cs | 5 +- .../Interfaces/IMemberTypeRepository.cs | 2 +- .../Repositories/MediaTypeRepository.cs | 40 ++++++-- .../Repositories/MemberTypeRepository.cs | 55 ++++++----- .../Services/ContentTypeService.cs | 10 +- .../Services/MemberTypeService.cs | 5 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Repositories/ContentTypeRepositoryTest.cs | 2 +- .../Repositories/MediaTypeRepositoryTest.cs | 2 +- .../Repositories/MemberTypeRepositoryTest.cs | 2 +- .../Cache/ContentTypeCacheRefresher.cs | 6 +- 15 files changed, 142 insertions(+), 141 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index fbc3069e5c..2f42919ce3 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -30,12 +30,9 @@ namespace Umbraco.Core.Persistence.Repositories protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) : base(work, cache, logger, sqlSyntax) - { - _guidRepo = new GuidReadOnlyContentTypeBaseRepository(this, work, cache, logger, sqlSyntax); + { } - - private readonly GuidReadOnlyContentTypeBaseRepository _guidRepo; - + /// /// Returns the content type ids that match the query /// @@ -1197,71 +1194,20 @@ AND umbracoNode.id <> @id", } - /// - /// Inner repository to support the GUID lookups and keep the caching consistent - /// - internal class GuidReadOnlyContentTypeBaseRepository : PetaPocoRepositoryBase - { - private readonly ContentTypeBaseRepository _parentRepo; - - public GuidReadOnlyContentTypeBaseRepository( - ContentTypeBaseRepository parentRepo, - IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) - : base(work, cache, logger, sqlSyntax) - { - _parentRepo = parentRepo; - } - - protected override TEntity PerformGet(Guid id) - { - return _parentRepo.PerformGet(id); - } - - protected override IEnumerable PerformGetAll(params Guid[] ids) - { - return _parentRepo.PerformGetAll(ids); - } - - protected override Sql GetBaseQuery(bool isCount) - { - return _parentRepo.GetBaseQuery(isCount); - } - - protected override string GetBaseWhereClause() - { - return "umbracoNode.uniqueID = @Id"; - } - - #region No implementation required - protected override IEnumerable PerformGetByQuery(IQuery query) - { - throw new NotImplementedException(); - } - - protected override IEnumerable GetDeleteClauses() - { - throw new NotImplementedException(); - } - - protected override Guid NodeObjectTypeId - { - get { throw new NotImplementedException(); } - } - - protected override void PersistNewItem(TEntity entity) - { - throw new NotImplementedException(); - } - - protected override void PersistUpdatedItem(TEntity entity) - { - throw new NotImplementedException(); - } - #endregion - } - protected abstract TEntity PerformGet(Guid id); + protected abstract TEntity PerformGet(string alias); protected abstract IEnumerable PerformGetAll(params Guid[] ids); + protected abstract bool PerformExists(Guid id); + + /// + /// Gets an Entity by alias + /// + /// + /// + public TEntity Get(string alias) + { + return PerformGet(alias); + } /// /// Gets an Entity by Id @@ -1270,7 +1216,7 @@ AND umbracoNode.id <> @id", /// public TEntity Get(Guid id) { - return _guidRepo.Get(id); + return PerformGet(id); } /// @@ -1278,9 +1224,12 @@ AND umbracoNode.id <> @id", /// /// /// - public IEnumerable GetAll(params Guid[] ids) + /// + /// Ensure explicit implementation, we don't want to have any accidental calls to this since it is essentially the same signature as the main GetAll when there are no parameters + /// + IEnumerable IReadRepository.GetAll(params Guid[] ids) { - return _guidRepo.GetAll(ids); + return PerformGetAll(ids); } /// @@ -1290,7 +1239,7 @@ AND umbracoNode.id <> @id", /// public bool Exists(Guid id) { - return _guidRepo.Exists(id); + return PerformExists(id); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 95601011fa..9b4f2cc44d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -27,15 +28,20 @@ namespace Umbraco.Core.Persistence.Repositories _templateRepository = templateRepository; } - #region Overrides of RepositoryBase - + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } + } + protected override IContentType PerformGet(int id) { - var contentTypes = ContentTypeQueryMapper.GetContentTypes( - new[] {id}, Database, SqlSyntax, this, _templateRepository); - - var contentType = contentTypes.SingleOrDefault(); - return contentType; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -244,25 +250,36 @@ namespace Umbraco.Core.Persistence.Repositories protected override IContentType PerformGet(Guid id) { - var contentTypes = ContentTypeQueryMapper.GetContentTypes( - new[] { id }, Database, SqlSyntax, this, _templateRepository); + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); + } - var contentType = contentTypes.SingleOrDefault(); - return contentType; + protected override IContentType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } protected override IEnumerable PerformGetAll(params Guid[] ids) { + //use the underlying GetAll which will force cache all content types + if (ids.Any()) { - return ContentTypeQueryMapper.GetContentTypes(ids, Database, SqlSyntax, this, _templateRepository); + return GetAll().Where(x => ids.Contains(x.Key)); } else { - var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - var allIds = Database.Fetch(sql).ToArray(); - return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); + return GetAll(); + //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + //var allIds = Database.Fetch(sql).ToArray(); + //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } + + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs new file mode 100644 index 0000000000..649726b100 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeCompositionRepository.cs @@ -0,0 +1,11 @@ +using System; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Persistence.Repositories +{ + public interface IContentTypeCompositionRepository : IRepositoryQueryable, IReadRepository + where TEntity : IContentTypeComposition + { + TEntity Get(string alias); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs index ca7e0e3c32..8aa3217ce1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentTypeRepository.cs @@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentTypeRepository : IRepositoryQueryable, IReadRepository + public interface IContentTypeRepository : IContentTypeCompositionRepository { /// /// Gets all entities of the specified query diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs index d28c59ac5b..4a82de7610 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaTypeRepository.cs @@ -1,11 +1,10 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMediaTypeRepository : IRepositoryQueryable, IReadRepository + public interface IMediaTypeRepository : IContentTypeCompositionRepository { /// /// Gets all entities of the specified query diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs index ae7739b28b..fc877d5227 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMemberTypeRepository.cs @@ -3,7 +3,7 @@ using Umbraco.Core.Models; namespace Umbraco.Core.Persistence.Repositories { - public interface IMemberTypeRepository : IRepositoryQueryable, IReadRepository + public interface IMemberTypeRepository : IContentTypeCompositionRepository { } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index bf963492d2..613ca623c7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; @@ -24,7 +25,15 @@ namespace Umbraco.Core.Persistence.Repositories { } - #region Overrides of RepositoryBase + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } + } protected override IMediaType PerformGet(int id) { @@ -168,25 +177,36 @@ namespace Umbraco.Core.Persistence.Repositories protected override IMediaType PerformGet(Guid id) { - var contentTypes = ContentTypeQueryMapper.GetMediaTypes( - new[] { id }, Database, SqlSyntax, this); - - var contentType = contentTypes.SingleOrDefault(); - return contentType; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); } protected override IEnumerable PerformGetAll(params Guid[] ids) { + //use the underlying GetAll which will force cache all content types + if (ids.Any()) { - return ContentTypeQueryMapper.GetMediaTypes(ids, Database, SqlSyntax, this); + return GetAll().Where(x => ids.Contains(x.Key)); } else { - var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - var allIds = Database.Fetch(sql).ToArray(); - return ContentTypeQueryMapper.GetMediaTypes(allIds, Database, SqlSyntax, this); + return GetAll(); + //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + //var allIds = Database.Fetch(sql).ToArray(); + //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } + + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } + + protected override IMediaType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 39f2a93082..b864c05a83 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using log4net; +using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; @@ -26,6 +27,16 @@ namespace Umbraco.Core.Persistence.Repositories { } + private FullDataSetRepositoryCachePolicyFactory _cachePolicyFactory; + protected override IRepositoryCachePolicyFactory CachePolicyFactory + { + get + { + //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + } + } + #region Overrides of RepositoryBase protected override IMemberType PerformGet(int id) @@ -262,38 +273,36 @@ namespace Umbraco.Core.Persistence.Repositories protected override IMemberType PerformGet(Guid id) { - var sql = GetBaseQuery(false); - sql.Where("umbracoNode.uniqueID = @Id", new { Id = id }); - sql.OrderByDescending(x => x.NodeId); - - var dtos = - Database.Fetch( - new PropertyTypePropertyGroupRelator().Map, sql); - - if (dtos == null || dtos.Any() == false) - return null; - - var factory = new MemberTypeReadOnlyFactory(); - var member = factory.BuildEntity(dtos.First()); - - return member; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Key == id); } protected override IEnumerable PerformGetAll(params Guid[] ids) { - var sql = GetBaseQuery(false); + //use the underlying GetAll which will force cache all content types + if (ids.Any()) { - var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.uniqueID='{0}'", x))); - sql.Where(statement); + return GetAll().Where(x => ids.Contains(x.Key)); } - sql.OrderByDescending(x => x.NodeId, SqlSyntax); + else + { + return GetAll(); + //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); + //var allIds = Database.Fetch(sql).ToArray(); + //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); + } + } - var dtos = - Database.Fetch( - new PropertyTypePropertyGroupRelator().Map, sql); + protected override bool PerformExists(Guid id) + { + return GetAll().FirstOrDefault(x => x.Key == id) != null; + } - return BuildFromDtos(dtos); + protected override IMemberType PerformGet(string alias) + { + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Alias.InvariantEquals(alias)); } /// diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index f144fa51e4..e6ead68810 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -158,10 +158,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateContentTypeRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Alias == alias); - var contentTypes = repository.GetByQuery(query); - - return contentTypes.FirstOrDefault(); + return repository.Get(alias); } } @@ -531,10 +528,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateMediaTypeRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Alias == alias); - var contentTypes = repository.GetByQuery(query); - - return contentTypes.FirstOrDefault(); + return repository.Get(alias); } } diff --git a/src/Umbraco.Core/Services/MemberTypeService.cs b/src/Umbraco.Core/Services/MemberTypeService.cs index 73061be4d0..c6bc9dd24a 100644 --- a/src/Umbraco.Core/Services/MemberTypeService.cs +++ b/src/Umbraco.Core/Services/MemberTypeService.cs @@ -69,10 +69,7 @@ namespace Umbraco.Core.Services { using (var repository = RepositoryFactory.CreateMemberTypeRepository(UowProvider.GetUnitOfWork())) { - var query = Query.Builder.Where(x => x.Alias == alias); - var contentTypes = repository.GetByQuery(query); - - return contentTypes.FirstOrDefault(); + return repository.Get(alias); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2bf00fcc9d..2ef0501f8e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -456,6 +456,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 1669b1af5e..f4471e6def 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -297,7 +297,7 @@ namespace Umbraco.Tests.Persistence.Repositories var allGuidIds = repository.GetAll().Select(x => x.Key).ToArray(); // Act - var contentTypes = repository.GetAll(allGuidIds); + var contentTypes = ((IReadRepository)repository).GetAll(allGuidIds); int count = DatabaseContext.Database.ExecuteScalar( "SELECT COUNT(*) FROM umbracoNode WHERE nodeObjectType = @NodeObjectType", diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index d0d66c5479..39b9475641 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -188,7 +188,7 @@ namespace Umbraco.Tests.Persistence.Repositories // Act - var mediaTypes = repository.GetAll(allGuidIds); + var mediaTypes = ((IReadRepository)repository).GetAll(allGuidIds); int count = DatabaseContext.Database.ExecuteScalar( diff --git a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs index dca6b4f9f1..aa4027d8a8 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MemberTypeRepositoryTest.cs @@ -115,7 +115,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(memberType2); unitOfWork.Commit(); - var result = repository.GetAll(memberType1.Key, memberType2.Key); + var result = ((IReadRepository)repository).GetAll(memberType1.Key, memberType2.Key); //there are 3 because of the Member type created for init data Assert.AreEqual(2, result.Count()); diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 9664681bb7..44a6efe9ff 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -128,7 +128,11 @@ namespace Umbraco.Web.Cache { ClearAllIsolatedCacheByEntityType(); ClearAllIsolatedCacheByEntityType(); - + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + ClearAllIsolatedCacheByEntityType(); + //all property type cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.PropertyTypeCacheKey); //all content type property cache From 2c399860ca071a78040aecac44fc55cff9873c94 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 22 Jan 2016 17:27:46 +0100 Subject: [PATCH 019/406] Fixes DisposableTimer so that the profiled step is disposed - and thus tracked/timed properly! --- src/Umbraco.Core/DisposableTimer.cs | 65 +++++++++++++++++++---------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Core/DisposableTimer.cs b/src/Umbraco.Core/DisposableTimer.cs index 816256360a..c7e8874449 100644 --- a/src/Umbraco.Core/DisposableTimer.cs +++ b/src/Umbraco.Core/DisposableTimer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Web; @@ -12,6 +13,12 @@ namespace Umbraco.Core /// public class DisposableTimer : DisposableObject { + private readonly ILogger _logger; + private readonly LogType? _logType; + private readonly IProfiler _profiler; + private readonly Type _loggerType; + private readonly string _endMessage; + private readonly IDisposable _profilerStep; private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private readonly Action _callback; @@ -25,25 +32,12 @@ namespace Umbraco.Core if (logger == null) throw new ArgumentNullException("logger"); if (loggerType == null) throw new ArgumentNullException("loggerType"); - _callback = x => - { - if (profiler != null) - { - profiler.DisposeIfDisposable(); - } - switch (logType) - { - case LogType.Debug: - logger.Debug(loggerType, () => endMessage + " (took " + x + "ms)"); - break; - case LogType.Info: - logger.Info(loggerType, () => endMessage + " (took " + x + "ms)"); - break; - default: - throw new ArgumentOutOfRangeException("logType"); - } - - }; + _logger = logger; + _logType = logType; + _profiler = profiler; + _loggerType = loggerType; + _endMessage = endMessage; + switch (logType) { case LogType.Debug: @@ -58,7 +52,7 @@ namespace Umbraco.Core if (profiler != null) { - profiler.Step(loggerType, startMessage); + _profilerStep = profiler.Step(loggerType, startMessage); } } @@ -223,7 +217,36 @@ namespace Umbraco.Core /// protected override void DisposeResources() { - _callback.Invoke(Stopwatch.ElapsedMilliseconds); + if (_profiler != null) + { + _profiler.DisposeIfDisposable(); + } + + if (_profilerStep != null) + { + _profilerStep.Dispose(); + } + + if (_logType.HasValue && _endMessage.IsNullOrWhiteSpace() == false && _loggerType != null && _logger != null) + { + switch (_logType) + { + case LogType.Debug: + _logger.Debug(_loggerType, () => _endMessage + " (took " + Stopwatch.ElapsedMilliseconds + "ms)"); + break; + case LogType.Info: + _logger.Info(_loggerType, () => _endMessage + " (took " + Stopwatch.ElapsedMilliseconds + "ms)"); + break; + default: + throw new ArgumentOutOfRangeException("logType"); + } + } + + if (_callback != null) + { + _callback.Invoke(Stopwatch.ElapsedMilliseconds); + } + } } From 20dc4f5bc4c4185eb1b33267dccd7f760a19ecab Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 23 Jan 2016 12:27:40 +0100 Subject: [PATCH 020/406] fixes merge issue/build --- .../Persistence/Repositories/ContentTypeRepository.cs | 4 +--- .../Persistence/Repositories/MediaTypeRepository.cs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 9b4f2cc44d..8ab98e86e4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -70,9 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories ? GetAll(dtos.DistinctBy(x => x.ContentTypeDto.NodeId).Select(x => x.ContentTypeDto.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - + /// /// Gets all entities of the specified query /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 613ca623c7..68f7a3c0d9 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -70,9 +70,7 @@ namespace Umbraco.Core.Persistence.Repositories ? GetAll(dtos.DistinctBy(x => x.NodeId).Select(x => x.NodeId).ToArray()) : Enumerable.Empty(); } - - #endregion - + /// /// Gets all entities of the specified query From b5e8cb29dd785ed979ec058d54d5b41b382e2b2e Mon Sep 17 00:00:00 2001 From: Shannon Date: Sat, 23 Jan 2016 12:38:08 +0100 Subject: [PATCH 021/406] ensure appctx and security isn't nulled on disposal (it's an application singleton it doesn't need to be nulled), creates new CreateContext method for creating standalone UmbracoContext instances. --- src/Umbraco.Web/UmbracoContext.cs | 57 ++++++++++++++++++++++--------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web/UmbracoContext.cs b/src/Umbraco.Web/UmbracoContext.cs index 1c688080ce..fca5ff2371 100644 --- a/src/Umbraco.Web/UmbracoContext.cs +++ b/src/Umbraco.Web/UmbracoContext.cs @@ -125,6 +125,7 @@ namespace Umbraco.Web if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + //if there's already a singleton, and we're not replacing then there's no need to ensure anything if (UmbracoContext.Current != null) { if (replaceContext == false) @@ -132,6 +133,39 @@ namespace Umbraco.Web UmbracoContext.Current._replacing = true; } + var umbracoContext = CreateContext(httpContext, applicationContext, webSecurity, umbracoSettings, urlProviders, preview ?? false); + + //assign the singleton + UmbracoContext.Current = umbracoContext; + return UmbracoContext.Current; + } + + /// + /// Creates a standalone UmbracoContext instance + /// + /// + /// + /// + /// + /// + /// + /// + /// A new instance of UmbracoContext + /// + public static UmbracoContext CreateContext( + HttpContextBase httpContext, + ApplicationContext applicationContext, + WebSecurity webSecurity, + IUmbracoSettingsSection umbracoSettings, + IEnumerable urlProviders, + bool preview) + { + if (httpContext == null) throw new ArgumentNullException("httpContext"); + if (applicationContext == null) throw new ArgumentNullException("applicationContext"); + if (webSecurity == null) throw new ArgumentNullException("webSecurity"); + if (umbracoSettings == null) throw new ArgumentNullException("umbracoSettings"); + if (urlProviders == null) throw new ArgumentNullException("urlProviders"); + var umbracoContext = new UmbracoContext( httpContext, applicationContext, @@ -142,15 +176,15 @@ namespace Umbraco.Web // create the RoutingContext, and assign var routingContext = new RoutingContext( umbracoContext, - + //TODO: Until the new cache is done we can't really expose these to override/mock new Lazy>(() => ContentFinderResolver.Current.Finders), new Lazy(() => ContentLastChanceFinderResolver.Current.Finder), - + // create the nice urls provider // there's one per request because there are some behavior parameters that can be changed new Lazy( - () => new UrlProvider( + () => new UrlProvider( umbracoContext, umbracoSettings.WebRouting, urlProviders), @@ -159,9 +193,7 @@ namespace Umbraco.Web //assign the routing context back umbracoContext.RoutingContext = routingContext; - //assign the singleton - UmbracoContext.Current = umbracoContext; - return UmbracoContext.Current; + return umbracoContext; } /// @@ -460,18 +492,9 @@ namespace Umbraco.Web protected override void DisposeResources() { Security.DisposeIfDisposable(); - Security = null; - _umbracoContext = null; - //ensure not to dispose this! - Application = null; - //Before we set these to null but in fact these are application lifespan singletons so - //there's no reason we need to set them to null and this also caused a problem with packages - //trying to access the cache properties on RequestEnd. - //http://issues.umbraco.org/issue/U4-2734 - //http://our.umbraco.org/projects/developer-tools/301-url-tracker/version-2/44327-Issues-with-URL-Tracker-in-614 - //ContentCache = null; - //MediaCache = null; + //If not running in a web ctx, ensure the thread based instance is nulled + _umbracoContext = null; } } } \ No newline at end of file From e91b53e66d3a8c8ecdee73d8f40a6a1276372646 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 25 Jan 2016 11:09:20 +0100 Subject: [PATCH 022/406] back ported: https://github.com/Umbraco/Umbraco-CMS/commit/2d0f198f58071f5113d5c3437f10f2b85220c348 --- .../Persistence/Repositories/ContentRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 85491b9f36..1f5cc1ecd4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -155,8 +155,8 @@ namespace Umbraco.Core.Persistence.Repositories "DELETE FROM cmsContentVersion WHERE ContentId = @Id", "DELETE FROM cmsContentXml WHERE nodeId = @Id", "DELETE FROM cmsContent WHERE nodeId = @Id", - "DELETE FROM umbracoNode WHERE id = @Id", - "DELETE FROM umbracoAccess WHERE nodeId = @Id" + "DELETE FROM umbracoAccess WHERE nodeId = @Id", + "DELETE FROM umbracoNode WHERE id = @Id" }; return list; } From 9d10362e7a80eaa8eb7e483d72f1cabcb4853713 Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 25 Jan 2016 18:17:48 +0100 Subject: [PATCH 023/406] U4-7642 - bugfix aggressive refactoring --- src/umbraco.cms/businesslogic/ContentType.cs | 52 ++++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/src/umbraco.cms/businesslogic/ContentType.cs b/src/umbraco.cms/businesslogic/ContentType.cs index 3cd51adaaa..b9b15ce595 100644 --- a/src/umbraco.cms/businesslogic/ContentType.cs +++ b/src/umbraco.cms/businesslogic/ContentType.cs @@ -636,8 +636,11 @@ namespace umbraco.cms.businesslogic { if (m_masterContentTypes == null) { - var ct = ApplicationContext.Current.Services.ContentTypeService.GetContentType(Id); - m_masterContentTypes = ct.CompositionPropertyGroups.Select(x => x.Id).ToList(); + // fixme - oops, what's this? + //var ct = ApplicationContext.Current.Services.ContentTypeService.GetContentType(Id); + //m_masterContentTypes = ct.CompositionPropertyGroups.Select(x => x.Id).ToList(); + + // back to normal m_masterContentTypes = ContentTypeItem == null ? new List() : ContentTypeItem.CompositionIds().ToList(); @@ -1307,7 +1310,7 @@ namespace umbraco.cms.businesslogic private void InitializeVirtualTabs() { // somewhat fixing... this whole class should be removed anyways - var ct = ContentTypeItem ?? ApplicationContext.Current.Services.ContentTypeService.GetContentType(Id); + var ct = ContentTypeItem ?? CallGetContentTypeMethod(Id); var tmp1 = ct.PropertyGroups .Select(x => (TabI) new Tab(x.Id, x.Name, x.SortOrder, this)) @@ -1316,6 +1319,27 @@ namespace umbraco.cms.businesslogic .DistinctBy(x => x.Id) .ToList(); _virtualTabs = tmp1; + + /* + // While we are initialising, we should not use the class-scoped list, as it may be used by other threads + var temporaryList = new List(); + foreach (PropertyTypeGroup ptg in PropertyTypeGroups.Where(x => x.ParentId == 0 && x.ContentTypeId == this.Id)) + temporaryList.Add(new Tab(ptg.Id, ptg.Name, ptg.SortOrder, this)); + + // Master Content Type + if (MasterContentTypes.Count > 0) + { + foreach (var mct in MasterContentTypes) + temporaryList.AddRange(GetContentType(mct).getVirtualTabs.ToList()); + } + + + // sort all tabs + temporaryList.Sort((a, b) => a.SortOrder.CompareTo(b.SortOrder)); + + // now that we aren't going to modify the list, we can set it to the class-scoped variable. + _virtualTabs = temporaryList.DistinctBy(x => x.Id).ToList(); + */ } private static void PopulateMasterContentTypes(PropertyType pt, int docTypeId) @@ -1480,13 +1504,31 @@ namespace umbraco.cms.businesslogic // regardless of the PropertyTypes belonging to the current ContentType. public List GetAllPropertyTypes() { - // somewhat fixing... this whole class should be removed anyways - var ct = _contenttype.ContentTypeItem ?? ApplicationContext.Current.Services.ContentTypeService.GetContentType(_contenttype.Id); + var ct = _contenttype.ContentTypeItem ?? _contenttype.CallGetContentTypeMethod(_contenttype.Id); return ct.CompositionPropertyTypes .OrderBy(x => x.SortOrder) .Select(x => x.Id) .Select(PropertyType.GetPropertyType) .ToList(); + + /* + var db = ApplicationContext.Current.DatabaseContext.Database; + var propertyTypeDtos = db.Fetch("WHERE propertyTypeGroupId = @Id", new { Id = _id }); + var tmp = propertyTypeDtos + .Select(propertyTypeDto => PropertyType.GetPropertyType(propertyTypeDto.Id)) + .ToList(); + + var propertyTypeGroupDtos = db.Fetch("WHERE parentGroupId = @Id", new { Id = _id }); + foreach (var propertyTypeGroupDto in propertyTypeGroupDtos) + { + var inheritedPropertyTypeDtos = db.Fetch("WHERE propertyTypeGroupId = @Id", new { Id = propertyTypeGroupDto.Id }); + tmp.AddRange(inheritedPropertyTypeDtos + .Select(propertyTypeDto => PropertyType.GetPropertyType(propertyTypeDto.Id)) + .ToList()); + } + + return tmp; + */ } /// From 3a17dd06d8e1b3e3e89fd98bede56d2cddcb19c2 Mon Sep 17 00:00:00 2001 From: Alexander Bryukhov Date: Tue, 26 Jan 2016 12:44:39 +0600 Subject: [PATCH 024/406] Latest update UI language ru.xml Only two new keys in the "sort" section translated. For full merged ru.xml file merge there are two more PRs (#1042 & #983) --- src/Umbraco.Web.UI/umbraco/config/lang/ru.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml index f6a2ff9c0f..8564fea033 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/ru.xml @@ -812,8 +812,8 @@ Вкладки - Sort order - Creation date + Порядок сортировки + Дата создания Сортировка завершена Перетаскивайте элементы на нужное место вверх или вниз для определения необходимого Вам порядка сортировки. Также можно щелкнуть по заголовкам столбцов, чтобы отсортировать все элементы сразу.
Не закрывайте это окно до окончания процесса сортировки.]]>
From 7fce8583c8981c9a5a2202764bf2aabb2226d925 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 26 Jan 2016 10:34:31 +0100 Subject: [PATCH 025/406] Fixes: U4-7701 Unable to Delete Member in Member Section --- .../propertyeditors/listview/listview.controller.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 0d42428334..678d6b777e 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 @@ -20,7 +20,8 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie getListResultsCallback = contentResource.getPagedResults; deleteItemCallback = contentResource.deleteByKey; getIdCallback = function(selected) { - return selected.key; + var selectedKey = getItemKey(selected.id); + return selectedKey; }; createEditUrlCallback = function(item) { return "/" + $scope.entityType + "/" + $scope.entityType + "/edit/" + item.key + "?page=" + $scope.options.pageNumber + "&listName=" + $scope.contentId; @@ -465,6 +466,15 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie return alias; } + function getItemKey(itemId) { + for (var i = 0; i < $scope.listViewResultSet.items.length; i++) { + var item = $scope.listViewResultSet.items[i]; + if (item.id === itemId) { + return item.key; + } + } + } + //GO! initView(); } From f30c05b37f6dd2fb35242e77047451cbb2becb9e Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 26 Jan 2016 10:47:36 +0100 Subject: [PATCH 026/406] add/remove semicolons --- .../listview/listview.controller.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 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 678d6b777e..79d5af93b5 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 @@ -276,28 +276,28 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie if (!(result.data && angular.isArray(result.data.notifications))) showNotificationsAndReset(result, true, getSuccessMsg(selected.length)); }); - }; + } $scope.delete = function () { applySelected( - function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])) }, - function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : "") }, - function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : "") }, + function (selected, index) { return deleteItemCallback(getIdCallback(selected[index])); }, + function (count, total) { return "Deleted " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Deleted " + total + " item" + (total > 1 ? "s" : ""); }, "Sure you want to delete?"); }; $scope.publish = function () { applySelected( function (selected, index) { return contentResource.publishById(getIdCallback(selected[index])); }, - function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : "") }, - function (total) { return "Published " + total + " item" + (total > 1 ? "s" : "") }); + function (count, total) { return "Published " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Published " + total + " item" + (total > 1 ? "s" : ""); }); }; $scope.unpublish = function() { applySelected( function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); }, - function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : "") }, - function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : "") }); + function (count, total) { return "Unpublished " + count + " out of " + total + " item" + (total > 1 ? "s" : ""); }, + function (total) { return "Unpublished " + total + " item" + (total > 1 ? "s" : ""); }); }; $scope.move = function() { @@ -381,7 +381,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie } return value; - }; + } /** This ensures that the correct value is set for each item in a row, we don't want to call a function during interpolation or ng-bind as performance is really bad that way */ function setPropertyValues(result) { @@ -416,14 +416,14 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie }); - }; + } function isDate(val) { if (angular.isString(val)) { return val.match(/^(\d{4})\-(\d{2})\-(\d{2})\ (\d{2})\:(\d{2})\:(\d{2})$/); } return false; - }; + } function initView() { //default to root id if the id is undefined @@ -438,7 +438,7 @@ function listViewController($rootScope, $scope, $routeParams, $injector, $cookie $scope.isTrashed = id === "-20" || id === "-21"; $scope.reloadView($scope.contentId); - }; + } function getLocalizedKey(alias) { From c33ad3eed74652bb554d475990ac168fa260635a Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 11:31:35 +0100 Subject: [PATCH 027/406] U4-7825 Remove Microsoft.AspNet.WebHelpers and Microsoft.AspNet.WebPages.Data dependencies from nuget --- build/NuSpecs/UmbracoCms.Core.nuspec | 2 -- 1 file changed, 2 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.Core.nuspec b/build/NuSpecs/UmbracoCms.Core.nuspec index 686fe16faf..607c35939c 100644 --- a/build/NuSpecs/UmbracoCms.Core.nuspec +++ b/build/NuSpecs/UmbracoCms.Core.nuspec @@ -36,8 +36,6 @@ - - From 738f24d6c9d58d672d7603665948828e7a496be1 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Tue, 19 Jan 2016 22:46:42 +0100 Subject: [PATCH 028/406] Hide search input behind split slider Ensure search input is covered by the split slider, when it is dragged all the way to the left similar to how tree nodes is covered. --- .../src/less/application/grid.less | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/application/grid.less b/src/Umbraco.Web.UI.Client/src/less/application/grid.less index 7029cc2a93..02f56e4046 100644 --- a/src/Umbraco.Web.UI.Client/src/less/application/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/application/grid.less @@ -92,17 +92,30 @@ body { } #search-form { - display: block; - margin: 0px; - z-index: 100; - position: absolute; - top: 0; - left: 0; - right: 0; -} + display: block; + margin: 0; + z-index: 100; + position: absolute; + top: 0; + left: 0; + right: 0; + overflow: hidden; -#search-form form{ - margin-top: 17px; + .form-search { + display: -ms-flexbox; + display: -webkit-box; + display: -webkit-flex; + display: flex; + margin-top: 17px; + + .umb-search-field { + width: auto; + min-width: 160px; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + } + } } #navigation { From fa1c227136d860b5beabd30e3af6f7978460f434 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 26 Jan 2016 12:01:04 +0100 Subject: [PATCH 029/406] U4-7724 Click on remove file(s) link, on an item in the media section, doesn't set dirty for form --- .../imagecropper/imagecropper.controller.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js index 42eee9b4a8..706f16fbbf 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/imagecropper/imagecropper.controller.js @@ -2,7 +2,7 @@ //with a specified callback, this callback will receive an object with a selection on it angular.module('umbraco') .controller("Umbraco.PropertyEditors.ImageCropperController", - function ($rootScope, $routeParams, $scope, $log, mediaHelper, cropperHelper, $timeout, editorState, umbRequestHelper, fileManager) { + function ($rootScope, $routeParams, $scope, $log, mediaHelper, cropperHelper, $timeout, editorState, umbRequestHelper, fileManager, angularHelper) { var config = angular.copy($scope.model.config); @@ -55,6 +55,10 @@ angular.module('umbraco') if ($scope.model.value) { delete $scope.model.value; } + + // set form to dirty to tricker discard changes dialog + var currForm = angularHelper.getCurrentForm($scope); + currForm.$setDirty(); }; //show previews @@ -145,4 +149,4 @@ angular.module('umbraco') return null; }); } - }); \ No newline at end of file + }); From 7d616cf937eee5aee3bd365db752c93d56ff3404 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 12:13:53 +0100 Subject: [PATCH 030/406] Ensures that all property data is deleted for a doc type when it is being deleted, updates the ChangeDocType to not use the legacy EnsuredPage, updates the non-legacy EnsuredPage.ClientTools to support the legacy logic. --- .../Repositories/ContentTypeRepository.cs | 16 ++++++ .../umbraco/dialogs/ChangeDocType.aspx | 2 +- .../umbraco/dialogs/ChangeDocType.aspx.cs | 26 ++++----- src/Umbraco.Web/UI/Pages/ClientTools.cs | 53 ++++++++++++++----- .../BasePages/ClientTools.cs | 7 ++- 5 files changed, 77 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index af2c1778c4..da6b0a6efb 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -182,6 +182,22 @@ namespace Umbraco.Core.Persistence.Repositories PersistDeletedItem((IEntity)child); } + //Before we call the base class methods to run all delete clauses, we need to first + // delete all of the property data associated with this document type. Normally this will + // be done in the ContentTypeService by deleting all associated content first, but in some cases + // like when we switch a document type, there is property data left over that is linked + // to the previous document type. So we need to ensure it's removed. + var sql = new Sql().Select("DISTINCT cmsPropertyData.propertytypeid") + .From(SqlSyntax) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, dto => dto.PropertyTypeId, dto => dto.Id) + .InnerJoin(SqlSyntax) + .On(SqlSyntax, dto => dto.NodeId, dto => dto.ContentTypeId) + .Where(dto => dto.NodeId == entity.Id); + + //Delete all cmsPropertyData where propertytypeid EXISTS in the subquery above + Database.Execute(SqlSyntax.GetDeleteSubquery("cmsPropertyData", "propertytypeid", sql)); + base.PersistDeletedItem(entity); } diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx index d119637384..683299e5be 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx @@ -115,7 +115,7 @@ <%= umbraco.ui.Text("or") %> - <%=umbraco.ui.Text("general", "cancel", this.getUser())%> + <%=umbraco.ui.Text("general", "cancel", Security.CurrentUser)%>

diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs index dc9e9b5ebc..f3769fbe1f 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs +++ b/src/Umbraco.Web.UI/umbraco/dialogs/ChangeDocType.aspx.cs @@ -4,9 +4,9 @@ using System.Linq; using System.Text; using System.Web.UI; using System.Web.UI.WebControls; -using umbraco.BasePages; using Umbraco.Core; using Umbraco.Core.Models; +using Umbraco.Web.UI.Pages; namespace Umbraco.Web.UI.Umbraco.Dialogs { @@ -25,7 +25,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs protected void Page_Load(object sender, EventArgs e) { var contentNodeId = int.Parse(Request.QueryString["id"]); - _content = ApplicationContext.Current.Services.ContentService.GetById(contentNodeId); + _content = Services.ContentService.GetById(contentNodeId); LocalizeTexts(); @@ -65,12 +65,12 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs private bool PopulateListOfValidAlternateDocumentTypes() { // Start with all content types - var documentTypes = ApplicationContext.Current.Services.ContentTypeService.GetAllContentTypes(); + var documentTypes = Services.ContentTypeService.GetAllContentTypes().ToArray(); // Remove invalid ones from list of potential alternatives - documentTypes = RemoveCurrentDocumentTypeFromAlternatives(documentTypes); - documentTypes = RemoveInvalidByParentDocumentTypesFromAlternatives(documentTypes); - documentTypes = RemoveInvalidByChildrenDocumentTypesFromAlternatives(documentTypes); + documentTypes = RemoveCurrentDocumentTypeFromAlternatives(documentTypes).ToArray(); + documentTypes = RemoveInvalidByParentDocumentTypesFromAlternatives(documentTypes).ToArray(); + documentTypes = RemoveInvalidByChildrenDocumentTypesFromAlternatives(documentTypes).ToArray(); // If we have at least one, bind to list and return true if (documentTypes.Any()) @@ -102,7 +102,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs else { // Below root, so only include those allowed as sub-nodes for the parent - var parentNode = ApplicationContext.Current.Services.ContentService.GetById(_content.ParentId); + var parentNode = Services.ContentService.GetById(_content.ParentId); return documentTypes .Where(x => parentNode.ContentType.AllowedContentTypes .Select(y => y.Id.Value) @@ -188,7 +188,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs private IContentType GetSelectedDocumentType() { - return ApplicationContext.Current.Services.ContentTypeService.GetContentType(int.Parse(NewDocumentTypeList.SelectedItem.Value)); + return Services.ContentTypeService.GetContentType(int.Parse(NewDocumentTypeList.SelectedItem.Value)); } private IEnumerable GetPropertiesOfContentType(IContentType contentType) @@ -196,7 +196,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs var properties = contentType.PropertyTypes.ToList(); while (contentType.ParentId > -1 && contentType.CompositionAliases().Any()) { - contentType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(contentType.ParentId); + contentType = Services.ContentTypeService.GetContentType(contentType.ParentId); properties.AddRange(contentType.PropertyTypes); } @@ -236,7 +236,7 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs if (NewTemplateList.SelectedItem != null) { var templateId = int.Parse(NewTemplateList.SelectedItem.Value); - _content.Template = templateId > 0 ? ApplicationContext.Current.Services.FileService.GetTemplate(templateId) : null; + _content.Template = templateId > 0 ? Services.FileService.GetTemplate(templateId) : null; } // Set the property values @@ -251,17 +251,17 @@ namespace Umbraco.Web.UI.Umbraco.Dialogs // Save var user = global::umbraco.BusinessLogic.User.GetCurrent(); - ApplicationContext.Current.Services.ContentService.Save(_content, user.Id); + Services.ContentService.Save(_content, user.Id); // Publish if the content was already published if (wasPublished) { - ApplicationContext.Current.Services.ContentService.Publish(_content, user.Id); + Services.ContentService.Publish(_content, user.Id); } // Sync the tree ClientTools.SyncTree(_content.Path, true); - + // Reload the page if the content was already being viewed ClientTools.ReloadContentFrameUrlIfPathLoaded("/editContent.aspx?id=" + _content.Id); diff --git a/src/Umbraco.Web/UI/Pages/ClientTools.cs b/src/Umbraco.Web/UI/Pages/ClientTools.cs index fe7205451f..ebb778183d 100644 --- a/src/Umbraco.Web/UI/Pages/ClientTools.cs +++ b/src/Umbraco.Web/UI/Pages/ClientTools.cs @@ -6,6 +6,7 @@ using Umbraco.Core.IO; using umbraco.BasePages; using System.Web.UI; using umbraco.BusinessLogic; +using Umbraco.Core; namespace Umbraco.Web.UI.Pages { @@ -47,7 +48,11 @@ namespace Umbraco.Web.UI.Pages public static string ChangeContentFrameUrl(string url) { return string.Format(ClientMgrScript + ".contentFrame('{0}');", url); } - public static string ChildNodeCreated = GetMainTree + ".childNodeCreated();"; + public static string ReloadContentFrameUrlIfPathLoaded(string url) + { + return string.Format(ClientMgrScript + ".reloadContentFrameUrlIfPathLoaded('{0}');", url); + } + public static string ChildNodeCreated = GetMainTree + ".childNodeCreated();"; public static string SyncTree { get { return GetMainTree + ".syncTree('{0}', {1});"; } } public static string ClearTreeCache { get { return GetMainTree + ".clearTreeCache();"; } } public static string CopyNode { get { return GetMainTree + ".copyNode('{0}', '{1}');"; } } @@ -146,23 +151,47 @@ namespace Umbraco.Web.UI.Pages //don't load if there is no url if (string.IsNullOrEmpty(url)) return this; - if (url.StartsWith("/") && !url.StartsWith(IOHelper.ResolveUrl(SystemDirectories.Umbraco))) - url = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/" + url; - - if (url.Trim().StartsWith("~")) - url = IOHelper.ResolveUrl(url); + url = EnsureUmbracoUrl(url); RegisterClientScript(Scripts.ChangeContentFrameUrl(url)); return this; } - /// - /// Shows the dashboard for the given application - /// - /// - /// - public ClientTools ShowDashboard(string app) + /// + /// Reloads the content in the content frame if the specified URL is currently loaded + /// + /// + public ClientTools ReloadContentFrameUrlIfPathLoaded(string url) + { + if (string.IsNullOrEmpty(url)) return this; + + url = EnsureUmbracoUrl(url); + + RegisterClientScript(Scripts.ReloadContentFrameUrlIfPathLoaded(url)); + + return this; + } + + private string EnsureUmbracoUrl(string url) + { + if (url.StartsWith("/") && url.StartsWith(IOHelper.ResolveUrl(SystemDirectories.Umbraco)) == false) + { + url = IOHelper.ResolveUrl(SystemDirectories.Umbraco).EnsureEndsWith('/') + url; + } + + if (url.Trim().StartsWith("~")) + url = IOHelper.ResolveUrl(url); + + return url; + } + + /// + /// Shows the dashboard for the given application + /// + /// + /// + public ClientTools ShowDashboard(string app) { return ChangeContentFrameUrl(SystemDirectories.Umbraco + string.Format("/dashboard.aspx?app={0}", app)); } diff --git a/src/umbraco.businesslogic/BasePages/ClientTools.cs b/src/umbraco.businesslogic/BasePages/ClientTools.cs index c164b086f1..627c4c1efb 100644 --- a/src/umbraco.businesslogic/BasePages/ClientTools.cs +++ b/src/umbraco.businesslogic/BasePages/ClientTools.cs @@ -6,6 +6,7 @@ using umbraco.BasePages; using System.Web.UI; using Umbraco.Core.IO; using umbraco.BusinessLogic; +using Umbraco.Core; namespace umbraco.BasePages { @@ -180,8 +181,12 @@ namespace umbraco.BasePages { if (url.StartsWith("/") && !url.StartsWith(IOHelper.ResolveUrl(SystemDirectories.Umbraco))) { - url = IOHelper.ResolveUrl(SystemDirectories.Umbraco) + url; + url = IOHelper.ResolveUrl(SystemDirectories.Umbraco).EnsureEndsWith('/') + url; } + + if (url.Trim().StartsWith("~")) + url = IOHelper.ResolveUrl(url); + return url; } From 3ed340bde1905b705ba44650efac450207132b33 Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Tue, 26 Jan 2016 13:29:39 +0100 Subject: [PATCH 031/406] Fixes weird skew in media grid --- .../src/less/components/umb-media-grid.less | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less index 43cb5a7f6e..0f2305bd90 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-media-grid.less @@ -9,17 +9,24 @@ } .umb-media-grid__item { - margin: 10px; - position: relative; - overflow: hidden; - cursor: pointer; - display: flex; - align-content: center; - align-items: center; - align-self: stretch; - box-shadow: 0 1px 1px 0 rgba(0,0,0,.2); - justify-content: center; - transition: box-shadow 100ms; + display: flex; + flex-direction: column; + flex-wrap: wrap; + + justify-content: center; + + align-content: center; + align-items: center; + align-self: stretch; + + margin: 10px; + position: relative; + overflow: hidden; + cursor: pointer; + + box-shadow: 0 1px 1px 0 rgba(0,0,0,.2); + + transition: box-shadow 100ms; } .umb-media-grid__item.-file { From ba9ebd394b75ed8b69c17b387aeed6854d2f4047 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 13:43:56 +0100 Subject: [PATCH 032/406] fixes MediaTypeRepositoryTest's --- .../Repositories/EntityContainerRepository.cs | 14 ++++++++++++++ .../Repositories/MediaTypeRepositoryTest.cs | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs index 5e8a6c2b5f..49d52ffffa 100644 --- a/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/EntityContainerRepository.cs @@ -131,6 +131,8 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistDeletedItem(EntityContainer entity) { + EnsureContainerType(entity); + var nodeDto = Database.FirstOrDefault(new Sql().Select("*") .From(SqlSyntax) .Where(dto => dto.NodeId == entity.Id && dto.NodeObjectType == entity.ContainerObjectType)); @@ -160,6 +162,8 @@ namespace Umbraco.Core.Persistence.Repositories protected override void PersistNewItem(EntityContainer entity) { + EnsureContainerType(entity); + entity.Name = entity.Name.Trim(); Mandate.ParameterNotNullOrEmpty(entity.Name, "entity.Name"); @@ -219,6 +223,8 @@ namespace Umbraco.Core.Persistence.Repositories // protected override void PersistUpdatedItem(EntityContainer entity) { + EnsureContainerType(entity); + entity.Name = entity.Name.Trim(); Mandate.ParameterNotNullOrEmpty(entity.Name, "entity.Name"); @@ -268,5 +274,13 @@ namespace Umbraco.Core.Persistence.Repositories entity.SortOrder = 0; entity.ResetDirtyProperties(); } + + private void EnsureContainerType(EntityContainer entity) + { + if (entity.ContainerObjectType != NodeObjectTypeId) + { + throw new InvalidOperationException("The container type does not match the repository object type"); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index 42d95bc487..1f8638db67 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -90,7 +90,7 @@ namespace Umbraco.Tests.Persistence.Repositories EntityContainer container; using (var containerRepository = CreateContainerRepository(unitOfWork)) { - container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) { Name = "blah" }; + container = new EntityContainer(Constants.ObjectTypes.MediaTypeGuid) { Name = "blah" }; containerRepository.AddOrUpdate(container); unitOfWork.Commit(); Assert.That(container.Id, Is.GreaterThan(0)); @@ -110,7 +110,7 @@ namespace Umbraco.Tests.Persistence.Repositories EntityContainer container; using (var containerRepository = CreateContainerRepository(unitOfWork)) { - container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) { Name = "blah" }; + container = new EntityContainer(Constants.ObjectTypes.MediaTypeGuid) { Name = "blah" }; containerRepository.AddOrUpdate(container); unitOfWork.Commit(); } @@ -135,7 +135,7 @@ namespace Umbraco.Tests.Persistence.Repositories using (var containerRepository = CreateContainerRepository(unitOfWork)) using (var repository = CreateRepository(unitOfWork)) { - var container = new EntityContainer(Constants.ObjectTypes.DataTypeGuid) { Name = "blah" }; + var container = new EntityContainer(Constants.ObjectTypes.MediaTypeGuid) { Name = "blah" }; containerRepository.AddOrUpdate(container); unitOfWork.Commit(); From 779e7d7667ce03d612e87261e0bf65d31234f994 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 13:46:55 +0100 Subject: [PATCH 033/406] fixes ContentTypeRepositoryTest --- .../Repositories/ContentTypeRepositoryTest.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs index 257c7c9612..05295bf3a1 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -62,9 +62,9 @@ namespace Umbraco.Tests.Persistence.Repositories return contentTypeRepository; } - private EntityContainerRepository CreateContainerRepository(IDatabaseUnitOfWork unitOfWork) + private EntityContainerRepository CreateContainerRepository(IDatabaseUnitOfWork unitOfWork, Guid containerEntityType) { - return new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, Constants.ObjectTypes.DocumentTypeContainerGuid); + return new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of(), SqlSyntax, containerEntityType); } //TODO Add test to verify SetDefaultTemplates updates both AllowedTemplates and DefaultTemplate(id). @@ -111,7 +111,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - using (var containerRepository = CreateContainerRepository(unitOfWork)) + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) using (var repository = CreateRepository(unitOfWork)) { var container1 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "blah1" }; @@ -158,14 +158,14 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; - using (var containerRepository = CreateContainerRepository(unitOfWork)) + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) { container = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "blah" }; containerRepository.AddOrUpdate(container); unitOfWork.Commit(); Assert.That(container.Id, Is.GreaterThan(0)); } - using (var containerRepository = CreateContainerRepository(unitOfWork)) + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) { var found = containerRepository.Get(container.Id); Assert.IsNotNull(found); @@ -178,19 +178,19 @@ namespace Umbraco.Tests.Persistence.Repositories var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; - using (var containerRepository = CreateContainerRepository(unitOfWork)) + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) { container = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "blah" }; containerRepository.AddOrUpdate(container); unitOfWork.Commit(); } - using (var containerRepository = CreateContainerRepository(unitOfWork)) + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) { // Act containerRepository.Delete(container); unitOfWork.Commit(); } - using (var containerRepository = CreateContainerRepository(unitOfWork)) + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid)) { var found = containerRepository.Get(container.Id); Assert.IsNull(found); @@ -202,7 +202,7 @@ namespace Umbraco.Tests.Persistence.Repositories { var provider = new PetaPocoUnitOfWorkProvider(Logger); var unitOfWork = provider.GetUnitOfWork(); - using (var containerRepository = CreateContainerRepository(unitOfWork)) + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.MediaTypeContainerGuid)) using (var repository = CreateRepository(unitOfWork)) { var container = new EntityContainer(Constants.ObjectTypes.MediaTypeGuid) { Name = "blah" }; @@ -225,7 +225,7 @@ namespace Umbraco.Tests.Persistence.Repositories var unitOfWork = provider.GetUnitOfWork(); EntityContainer container; IMediaType contentType; - using (var containerRepository = CreateContainerRepository(unitOfWork)) + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.MediaTypeContainerGuid)) using (var repository = CreateMediaTypeRepository(unitOfWork)) { container = new EntityContainer(Constants.ObjectTypes.MediaTypeGuid) { Name = "blah" }; @@ -237,7 +237,7 @@ namespace Umbraco.Tests.Persistence.Repositories repository.AddOrUpdate(contentType); unitOfWork.Commit(); } - using (var containerRepository = CreateContainerRepository(unitOfWork)) + using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.MediaTypeContainerGuid)) using (var repository = CreateMediaTypeRepository(unitOfWork)) { // Act From 8cb9d6858e17091be6b0a2443ee47469ece8dd73 Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Tue, 26 Jan 2016 14:04:29 +0100 Subject: [PATCH 034/406] U4-7722 Show checkmark when hover image in media grid --- .../src/views/components/umb-media-grid.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index 6036234140..d62435ac28 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -10,11 +10,11 @@
- {{item.name}} - {{item.name}} + {{item.name}} + {{item.name}} - \ No newline at end of file + From 24f38e0c0e73e0246fd7cff1ffb77bc001d56069 Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Tue, 26 Jan 2016 14:17:59 +0100 Subject: [PATCH 035/406] Fixed illustration width in safari --- .../src/views/components/upload/umb-file-dropzone.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html index 7c156d1095..c1d5ad9de8 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html @@ -19,7 +19,7 @@
- + From aeb6babad0df1ac3b3a4f98151d72ac972062aff Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 14:24:07 +0100 Subject: [PATCH 036/406] fixes template unit tests --- .../Persistence/Repositories/TemplateRepositoryTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 4fae9938cb..864571d5df 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -178,7 +178,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(_viewsFileSystem.FileExists("test.cshtml"), Is.True); Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ - Layout = null; +" + "\t" + @"Layout = null; }", template.Content); } @@ -208,7 +208,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(_viewsFileSystem.FileExists("test2.cshtml"), Is.True); Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ - Layout = ""test.cshtml""; +" + "\t" + @"Layout = ""test.cshtml""; }", template2.Content); } From 7cdaacf1498b2acaff03d3f3641997fdd94a0a19 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 14:26:16 +0100 Subject: [PATCH 037/406] adds assert --- .../Persistence/Repositories/MediaTypeRepositoryTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs index 1f8638db67..eaedef09e4 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaTypeRepositoryTest.cs @@ -113,6 +113,7 @@ namespace Umbraco.Tests.Persistence.Repositories container = new EntityContainer(Constants.ObjectTypes.MediaTypeGuid) { Name = "blah" }; containerRepository.AddOrUpdate(container); unitOfWork.Commit(); + Assert.That(container.Id, Is.GreaterThan(0)); } using (var containerRepository = CreateContainerRepository(unitOfWork)) { From ba2fd96e88de76eb0fe2a50371603ffc081aca38 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 15:54:17 +0100 Subject: [PATCH 038/406] U4-7695 Media items are not updated when a new crop is added to Image Cropper --- .../ValueConverters/GridValueConverter.cs | 5 +- .../ImageCropperValueConverter.cs | 67 +++++++++++++++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../propertyeditors/grid/grid.controller.js | 4 +- .../ImageCropperTemplateExtensions.cs | 4 ++ 5 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs index 11f915fabd..b3db026c89 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs @@ -12,6 +12,9 @@ using Umbraco.Core.Models.PublishedContent; namespace Umbraco.Core.PropertyEditors.ValueConverters { + /// + /// This ensures that the grid config is merged in with the front-end value + /// [DefaultPropertyValueConverter(typeof(JsonValueConverter))] //this shadows the JsonValueConverter [PropertyValueType(typeof(JToken))] [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] @@ -90,7 +93,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters } catch (Exception ex) { - LogHelper.Error("Could not parse the string " + sourceString + " to a json object", ex); + LogHelper.Error("Could not parse the string " + sourceString + " to a json object", ex); } } diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs new file mode 100644 index 0000000000..8ad0b7d69d --- /dev/null +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/ImageCropperValueConverter.cs @@ -0,0 +1,67 @@ +using System; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Umbraco.Core.Logging; +using Umbraco.Core.Models.PublishedContent; + +namespace Umbraco.Core.PropertyEditors.ValueConverters +{ + /// + /// This ensures that the cropper config (pre-values/crops) are merged in with the front-end value. + /// + [DefaultPropertyValueConverter(typeof (JsonValueConverter))] //this shadows the JsonValueConverter + [PropertyValueType(typeof (JToken))] + [PropertyValueCache(PropertyCacheValue.All, PropertyCacheLevel.Content)] + public class ImageCropperValueConverter : JsonValueConverter + { + public override bool IsConverter(PublishedPropertyType propertyType) + { + return propertyType.PropertyEditorAlias.InvariantEquals(Constants.PropertyEditors.ImageCropperAlias); + } + + public override object ConvertDataToSource(PublishedPropertyType propertyType, object source, bool preview) + { + if (source == null) return null; + var sourceString = source.ToString(); + + if (sourceString.DetectIsJson()) + { + JObject obj; + try + { + obj = JsonConvert.DeserializeObject(sourceString); + } + catch (Exception ex) + { + LogHelper.Error("Could not parse the string " + sourceString + " to a json object", ex); + return sourceString; + } + + //need to lookup the pre-values for this data type + //TODO: Change all singleton access to use ctor injection in v8!!! + var dt = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(propertyType.DataTypeId); + + if (dt != null && dt.IsDictionaryBased && dt.PreValuesAsDictionary.ContainsKey("crops")) + { + var cropsString = dt.PreValuesAsDictionary["crops"].Value; + JArray crops; + try + { + crops = JsonConvert.DeserializeObject(cropsString); + } + catch (Exception ex) + { + LogHelper.Error("Could not parse the string " + cropsString + " to a json object", ex); + return sourceString; + } + obj["crops"] = crops; + } + + return obj; + } + + //it's not json, just return the string + return sourceString; + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3476d1fcc3..cee2f8ee3f 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -488,6 +488,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js index 8272f032ee..29176da102 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/grid.controller.js @@ -580,11 +580,11 @@ angular.module("umbraco") // ********************************************* - // INITIALISATION + // Initialization // these methods are called from ng-init on the template // so we can controll their first load data // - // intialisation sets non-saved data like percentage sizing, allowed editors and + // intialization sets non-saved data like percentage sizing, allowed editors and // other data that should all be pre-fixed with $ to strip it out on save // ********************************************* diff --git a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs index a76b39f187..e592c157aa 100644 --- a/src/Umbraco.Web/ImageCropperTemplateExtensions.cs +++ b/src/Umbraco.Web/ImageCropperTemplateExtensions.cs @@ -119,6 +119,10 @@ if (mediaItem.HasProperty(propertyAlias) && mediaItem.HasValue(propertyAlias)) { + //TODO: We should change this, the default value is JObject now, this means we are ToString() ing the value, + // then re-deserializing it back to json and then to a strongly typed model. + // With a tiny bit of work we can make this more efficient since it's already a JObject. + imageCropperValue = mediaItem.GetPropertyValue(propertyAlias); // get the raw value (this will be json) From e461cb92da18c0f9cb99087de98e76251bac5242 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 26 Jan 2016 16:07:44 +0100 Subject: [PATCH 039/406] U4-7787 Shorthand use of ?altTemplate= doesn't work #U4-7787 Fixed --- src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 955e316bc3..acc17b370a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -475,7 +475,7 @@ namespace Umbraco.Core.Persistence.Repositories if (aliases.Any() == false) return base.GetAll(); //return from base.GetAll, this is all cached - return base.GetAll().Where(x => aliases.Contains(x.Alias)); + return base.GetAll().Where(x => aliases.InvariantContains(x.Alias)); } public IEnumerable GetChildren(int masterTemplateId) From fd58c3b3c6b6f0358dd75d03488e323735f70bfb Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 26 Jan 2016 16:09:48 +0100 Subject: [PATCH 040/406] Fixes: U4-7815 List view - bug when custom list view has been created. --- src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs index d49c40bdaf..7040ee18d3 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentTypeModelMapper.cs @@ -152,9 +152,9 @@ namespace Umbraco.Web.Models.Mapping //default listview dest.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Content"; - if (string.IsNullOrEmpty(source.Name) == false) + if (string.IsNullOrEmpty(source.Alias) == false) { - var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Name; + var name = Constants.Conventions.DataTypes.ListViewPrefix + source.Alias; if (applicationContext.Services.DataTypeService.GetDataTypeDefinitionByName(name) != null) dest.ListViewEditorName = name; } From 74ddd09c5d325ed788e4a5f6116d92862e65e7d2 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 17:05:46 +0100 Subject: [PATCH 041/406] U4-7836 YSOD when upgrading Umbraco with error during AddUserDataClaims - this also ensures that no empty/null sections can be added to the user object since this is where the original exception was coming from. --- .../Persistence/Relators/UserSectionRelator.cs | 9 ++++++--- .../Install/InstallSteps/SetUmbracoVersionStep.cs | 11 ++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs b/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs index 859fa7cf5a..923348e729 100644 --- a/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs +++ b/src/Umbraco.Core/Persistence/Relators/UserSectionRelator.cs @@ -18,8 +18,11 @@ namespace Umbraco.Core.Persistence.Relators // Is this the same DictionaryItem as the current one we're processing if (Current != null && Current.Id == a.Id) { - // Yes, just add this User2AppDto to the current item's collection - Current.User2AppDtos.Add(p); + if (p.AppAlias.IsNullOrWhiteSpace() == false) + { + // Yes, just add this User2AppDto to the current item's collection + Current.User2AppDtos.Add(p); + } // Return null to indicate we're not done with this User yet return null; @@ -35,7 +38,7 @@ namespace Umbraco.Core.Persistence.Relators Current = a; Current.User2AppDtos = new List(); //this can be null since we are doing a left join - if (p.AppAlias != null) + if (p.AppAlias.IsNullOrWhiteSpace() == false) { Current.User2AppDtos.Add(p); } diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index 6523925505..f7c9b3d6a5 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -40,9 +40,14 @@ namespace Umbraco.Web.Install.InstallSteps var clientDependencyConfig = new ClientDependencyConfiguration(_applicationContext.ProfilingLogger.Logger); var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber(); - var security = new WebSecurity(_httpContext, _applicationContext); - security.PerformLogin(0); - + //During a new install we'll log the default user in (which is id = 0). + // During an upgrade, the user will already need to be logged in in order to run the installer. + if (InstallTypeTarget == InstallationType.NewInstall) + { + var security = new WebSecurity(_httpContext, _applicationContext); + security.PerformLogin(0); + } + //reports the ended install var ih = new InstallHelper(UmbracoContext.Current); ih.InstallStatus(true, ""); From 72900ef3b3d996cc32e7681afd9241b847dbe06e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 17:56:25 +0100 Subject: [PATCH 042/406] U4-7813 Enable MiniProfiler to profiler during startup Conflicts: src/Umbraco.Core/Umbraco.Core.csproj --- .../Profiling/StartupWebProfilerProvider.cs | 126 ++++++++++++++++++ src/Umbraco.Core/Profiling/WebProfiler.cs | 33 +++-- src/Umbraco.Core/Umbraco.Core.csproj | 1 + src/Umbraco.Web/WebBootManager.cs | 4 +- 4 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs diff --git a/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs b/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs new file mode 100644 index 0000000000..16ce638c0e --- /dev/null +++ b/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs @@ -0,0 +1,126 @@ +using System.Threading; +using System.Web; +using StackExchange.Profiling; + +namespace Umbraco.Core.Profiling +{ + /// + /// Allows us to profile items during app startup - before an HttpRequest is created + /// + internal class StartupWebProfilerProvider : WebRequestProfilerProvider + { + public StartupWebProfilerProvider() + { + _startupPhase = StartupPhase.Boot; + //create the startup profiler + _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup", ProfileLevel.Verbose) + { + Name = "StartupProfiler" + }; + } + + private MiniProfiler _startupProfiler; + private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); + + private enum StartupPhase + { + None = 0, + Boot = 1, + Request = 2 + } + + private volatile StartupPhase _startupPhase; + + public void BootComplete() + { + using (new ReadLock(_locker)) + { + if (_startupPhase != StartupPhase.Boot) return; + } + + using (var l = new UpgradeableReadLock(_locker)) + { + if (_startupPhase == StartupPhase.Boot) + { + l.UpgradeToWriteLock(); + + ////Now we need to transfer some information from our startup phase to the normal + ////web request phase to output the startup profiled information. + ////Stop our internal startup profiler, this will write out it's results to storage. + //StopProfiler(_startupProfiler); + //SaveProfiler(_startupProfiler); + + _startupPhase = StartupPhase.Request; + } + } + } + + public override void Stop(bool discardResults) + { + using (new ReadLock(_locker)) + { + if (_startupPhase == StartupPhase.None) + { + base.Stop(discardResults); + return; + } + } + + using (var l = new UpgradeableReadLock(_locker)) + { + if (_startupPhase > 0 && base.GetCurrentProfiler() == null) + { + l.UpgradeToWriteLock(); + + _startupPhase = StartupPhase.None; + + if (HttpContext.Current != null) + { + HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; + base.Stop(discardResults); + _startupProfiler = null; + } + } + else + { + base.Stop(discardResults); + } + } + } + + public override MiniProfiler Start(ProfileLevel level) + { + using (new ReadLock(_locker)) + { + if (_startupPhase > 0 && base.GetCurrentProfiler() == null) + { + SetProfilerActive(_startupProfiler); + return _startupProfiler; + } + + return base.Start(level); + } + } + + public override MiniProfiler GetCurrentProfiler() + { + using (new ReadLock(_locker)) + { + if (_startupPhase > 0) + { + try + { + var current = base.GetCurrentProfiler(); + if (current == null) return _startupProfiler; + } + catch + { + return _startupProfiler; + } + } + + return base.GetCurrentProfiler(); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Profiling/WebProfiler.cs b/src/Umbraco.Core/Profiling/WebProfiler.cs index 00d088bca7..7e2cf49313 100644 --- a/src/Umbraco.Core/Profiling/WebProfiler.cs +++ b/src/Umbraco.Core/Profiling/WebProfiler.cs @@ -12,15 +12,24 @@ namespace Umbraco.Core.Profiling ///
internal class WebProfiler : IProfiler { + private StartupWebProfilerProvider _startupWebProfilerProvider; /// /// Constructor - /// - /// - /// Binds to application events to enable the MiniProfiler - /// + ///
internal WebProfiler() { + //setup some defaults + MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); + MiniProfiler.Settings.StackMaxLength = 5000; + + //At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext + // since it hasn't started yet. So we need to do some hacking to enable profiling during startup. + _startupWebProfilerProvider = new StartupWebProfilerProvider(); + //this should always be the case during startup, we'll need to set a custom profiler provider + MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider; + + //Binds to application events to enable the MiniProfiler with a real HttpRequest UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; } @@ -53,7 +62,12 @@ namespace Umbraco.Core.Profiling /// void UmbracoApplicationEndRequest(object sender, EventArgs e) { - if (CanPerformProfilingAction(sender)) + if (_startupWebProfilerProvider != null) + { + Stop(); + _startupWebProfilerProvider = null; + } + else if (CanPerformProfilingAction(sender)) { Stop(); } @@ -66,6 +80,11 @@ namespace Umbraco.Core.Profiling /// void UmbracoApplicationBeginRequest(object sender, EventArgs e) { + if (_startupWebProfilerProvider != null) + { + _startupWebProfilerProvider.BootComplete(); + } + if (CanPerformProfilingAction(sender)) { Start(); @@ -124,9 +143,7 @@ namespace Umbraco.Core.Profiling /// Start the profiler ///
public void Start() - { - MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); - MiniProfiler.Settings.StackMaxLength = 5000; + { MiniProfiler.Start(); } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2ef0501f8e..d11270592a 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -469,6 +469,7 @@ + diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index dcf1e98be4..a9495afa7c 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -183,7 +183,9 @@ namespace Umbraco.Web base.InitializeProfilerResolver(); //Set the profiler to be the web profiler - ProfilerResolver.Current.SetProfiler(new WebProfiler()); + var profiler = new WebProfiler(); + ProfilerResolver.Current.SetProfiler(profiler); + profiler.Start(); } /// From 19bc97a7fd2bc864dd9419e63420ba94076d795e Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 18:56:29 +0100 Subject: [PATCH 043/406] Removes the static (hack) cache for content types for creating published content --- .../PublishedContent/PublishedContentType.cs | 58 ++----------------- .../Cache/ContentTypeCacheRefresher.cs | 8 +-- .../Cache/DataTypeCacheRefresher.cs | 1 - 3 files changed, 7 insertions(+), 60 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 5f30c08ce7..3cff4f0298 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -98,45 +98,10 @@ namespace Umbraco.Core.Models.PublishedContent #endregion - #region Cache - - // these methods are called by ContentTypeCacheRefresher and DataTypeCacheRefresher - - internal static void ClearAll() - { - Logging.LogHelper.Debug("Clear all."); - // ok and faster to do it by types, assuming noone else caches PublishedContentType instances - //ApplicationContext.Current.ApplicationCache.ClearStaticCacheByKeySearch("PublishedContentType_"); - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes(); - } - - internal static void ClearContentType(int id) - { - Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id); - // requires a predicate because the key does not contain the ID - // faster than key strings comparisons anyway - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => value.Id == id); - } - - internal static void ClearDataType(int id) - { - Logging.LogHelper.Debug("Clear data type w/id {0}.", () => id); - // there is no recursion to handle here because a PublishedContentType contains *all* its - // properties ie both its own properties and those that were inherited (it's based upon an - // IContentTypeComposition) and so every PublishedContentType having a property based upon - // the cleared data type, be it local or inherited, will be cleared. - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( - (key, value) => value.PropertyTypes.Any(x => x.DataTypeId == id)); - } - + public static PublishedContentType Get(PublishedItemType itemType, string alias) { - var key = string.Format("PublishedContentType_{0}_{1}", - itemType.ToString().ToLowerInvariant(), alias.ToLowerInvariant()); - - var type = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItem(key, - () => CreatePublishedContentType(itemType, alias)); + var type = CreatePublishedContentType(itemType, alias); return type; } @@ -169,21 +134,8 @@ namespace Umbraco.Core.Models.PublishedContent return new PublishedContentType(contentType); } - // for unit tests - changing the callback must reset the cache obviously - private static Func _getPublishedContentTypeCallBack; - internal static Func GetPublishedContentTypeCallback - { - get { return _getPublishedContentTypeCallBack; } - set - { - // see note above - //ClearAll(); - ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheByKeySearch("PublishedContentType_"); - - _getPublishedContentTypeCallBack = value; - } - } - - #endregion + // for unit tests + internal static Func GetPublishedContentTypeCallback { get; set; } + } } diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index 44a6efe9ff..c44b3f2b51 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -141,9 +141,7 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.ContentTypeCacheKey); //clear static object cache global::umbraco.cms.businesslogic.ContentType.RemoveAllDataTypeCache(); - - PublishedContentType.ClearAll(); - + base.RefreshAll(); } @@ -280,9 +278,7 @@ namespace Umbraco.Web.Cache //clears the dictionary object cache of the legacy ContentType global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(payload.Alias); - - PublishedContentType.ClearContentType(payload.Id); - + //need to recursively clear the cache for each child content type foreach (var descendant in payload.DescendantPayloads) { diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index 11b3ab6294..173f4dcb86 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -108,7 +108,6 @@ namespace Umbraco.Web.Cache if (dataTypeCache) dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); - PublishedContentType.ClearDataType(payload.Id); }); base.Refresh(jsonPayload); From 7a25cb52b218510c1c2230e64df56168601125a1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 26 Jan 2016 19:13:42 +0100 Subject: [PATCH 044/406] updates the repository cache policies so that we aren't keeping everything in cache unnecessarily --- .../Cache/DefaultRepositoryCachePolicy.cs | 17 ++++++++++++++--- .../Cache/FullDataSetRepositoryCachePolicy.cs | 4 ++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 66d9b2ac25..45e79a1b67 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -11,6 +11,10 @@ namespace Umbraco.Core.Cache /// /// /// + /// + /// This cache policy uses sliding expiration and caches instances for 5 minutes. However if allow zero count is true, then we use the + /// default policy with no expiry. + /// internal class DefaultRepositoryCachePolicy : DisposableObject, IRepositoryCachePolicy where TEntity : class, IAggregateRoot { @@ -54,7 +58,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (entity.HasIdentity) { - Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity); + Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared @@ -225,7 +231,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (entity.HasIdentity) { - Cache.InsertCacheItem(cacheKey, () => entity); + Cache.InsertCacheItem(cacheKey, () => entity, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } }); } @@ -244,6 +252,7 @@ namespace Umbraco.Core.Cache { //there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache // to signify that there is a zero count cache + //NOTE: Don't set expiry/sliding for a zero count Cache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {}); } else @@ -256,7 +265,9 @@ namespace Umbraco.Core.Cache //just to be safe, we cannot cache an item without an identity if (localCopy.HasIdentity) { - Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy); + Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy, + timeout: TimeSpan.FromMinutes(5), + isSliding: true); } } } diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index 40b100ef67..3b3c98fc80 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -11,6 +11,10 @@ namespace Umbraco.Core.Cache ///
/// /// + /// + /// This caching policy has no sliding expiration but uses the default ObjectCache.InfiniteAbsoluteExpiration as it's timeout, so it + /// should not leave the cache unless the cache memory is exceeded and it gets thrown out. + /// internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IAggregateRoot { From 21190bc0c6a6cb8774da4bb58abab3f4e1344bde Mon Sep 17 00:00:00 2001 From: Chriztian Steinmeier Date: Tue, 26 Jan 2016 19:36:12 +0100 Subject: [PATCH 045/406] Change the initial wording of the sort-direction button Keep it lowercased as that's what it actually toggles between when clicked --- .../views/common/dialogs/template/querybuilder.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/template/querybuilder.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/template/querybuilder.controller.js index 7fd15bd715..6dae664a3d 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/template/querybuilder.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/template/querybuilder.controller.js @@ -33,7 +33,7 @@ angular.module("umbraco").controller('Umbraco.Dialogs.Template.QueryBuilderContr alias: "", name: "", }, - direction: "Ascending" + direction: "ascending" } }; From c9f71ce067df177c8d5cf85d4ebc3963e726bed3 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 26 Jan 2016 21:07:15 +0100 Subject: [PATCH 046/406] Fixes: U4-7772 Cursor jumps when editing alias in new ContentType editor + U4-7832 Editing property alias chomps 1st character --- .../forms/umbselectwhen.directive.js | 28 +++++++++++++++++++ .../components/umblockedfield.directive.js | 28 ++----------------- .../views/components/umb-locked-field.html | 5 +++- 3 files changed, 34 insertions(+), 27 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js new file mode 100644 index 0000000000..b8986c02d3 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/forms/umbselectwhen.directive.js @@ -0,0 +1,28 @@ +(function() { + 'use strict'; + + function SelectWhen($timeout) { + + function link(scope, el, attr, ctrl) { + + attr.$observe("umbSelectWhen", function(newValue) { + if (newValue === "true") { + $timeout(function() { + el.select(); + }); + } + }); + + } + + var directive = { + restrict: 'A', + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbSelectWhen', SelectWhen); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js index 870a7d45d6..b382fa1c32 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js @@ -24,12 +24,10 @@ // scope object, but that would mean we'd have to watch that value too in order to set the outer // ngModelCtrl.$modelValue. It's seems like less overhead to just do this and not have 2x watches. scope.lockedFieldForm.lockedField.$modelValue = undefined; - scope.lockedFieldForm.lockedField.$setViewValue(newValue); - scope.lockedFieldForm.lockedField.$render(); + scope.lockedFieldForm.lockedField.$render(); } + scope.lockedFieldForm.lockedField.$setViewValue(scope.lockedFieldForm.lockedField.$modelValue); }); - - var input = el.find('.umb-locked-field__input'); function activate() { @@ -57,36 +55,14 @@ scope.lock = function() { scope.locked = true; - input.unbind("blur"); }; scope.unlock = function() { scope.locked = false; - autoFocusField(); }; - function autoFocusField() { - - var onBlurHandler = function() { - scope.$apply(function(){ - scope.lock(); - }); - }; - - $timeout(function() { - input.focus(); - input.select(); - input.on("blur", onBlurHandler); - }); - - } - activate(); - scope.$on('$destroy', function() { - input.unbind('blur'); - }); - } var directive = { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html index 744e635f21..217d420a3a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-locked-field.html @@ -21,7 +21,10 @@ umb-auto-resize required val-server-field="{{serverValidationField}}" - title="{{ngModel}}" /> + title="{{ngModel}}" + focus-when="{{!locked}}" + umb-select-when="{{!locked}}" + on-blur="lock()" /> From 5e88fbfad0309c8ef05b49e4dac2d86ba048c11e Mon Sep 17 00:00:00 2001 From: bjarnef Date: Tue, 26 Jan 2016 23:52:16 +0100 Subject: [PATCH 047/406] Indent of toolbar options in TinyMCE config --- src/Umbraco.Web.UI.Client/src/less/main.less | 35 +++++++++++++++---- .../propertyeditors/rte/rte.prevalues.html | 34 ++++++++++-------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 4502527ce8..09f916c59f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -140,7 +140,10 @@ h5.-black { /* LABELS*/ .umb-control-group label.control-label { - text-align: left + text-align: left; +} +.umb-control-group label.control-label > div > label { + padding-left: 0; } .umb-control-group label .help-block, .umb-control-group label small { @@ -150,7 +153,7 @@ h5.-black { padding-top: 5px; } .umb-nolabel .controls { - margin-left: 0px; + margin-left: 0; } .controls-row { @@ -159,11 +162,15 @@ h5.-black { } .umb-user-panel .controls-row { - margin-left: 0px; + margin-left: 0; } .controls-row label { - display: inline-block + display: inline-block; +} + +.controls-row > div > label { + padding-left: 0; } .block-form .controls-row { @@ -171,10 +178,24 @@ h5.-black { padding-top: 0; } -.hidelabel > div > .controls-row, .hidelabel > .controls-row, .hidelabel .controls { - padding: 0px; +.hidelabel > div > .controls-row, .hidelabel > .controls-row, .hidelabel > div > .controls { + padding: 0; border: none; - margin: 0px !important; + margin: 0 !important; +} + +.controls-row > .vertical-align-items { + display: flex; + align-items: center; +} + +.controls-row > .vertical-align-items > input.umb-editor-tiny { + margin-left: 5px; + margin-right: 5px; +} + +.controls-row > .vertical-align-items > input.umb-editor-tiny:first-child { + margin-left: 0; } .thumbnails .selected { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html index f00dfa06c1..fbc97224ab 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html @@ -2,35 +2,41 @@
- +
- - {{css.name}} +
- × - Pixels +
+ × + Pixels +
- Pixels +
+ Pixels +
\ No newline at end of file From 43e49b75de00080238509b2df7706bca7255705a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 09:21:58 +0100 Subject: [PATCH 048/406] Add extra div to media grid to fix firefox issue in chrome --- .../src/views/components/umb-media-grid.html | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html index d62435ac28..5cd6d5c82f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-media-grid.html @@ -1,20 +1,20 @@
+
+
-
+ - +
+ +
{{item.name}}
+
-
- -
{{item.name}}
-
+
+ {{item.name}} + {{item.name}} -
- {{item.name}} - {{item.name}} - - - -
+ +
+
From c33b488006b49fb12f6f1c80795eb8695d5b1536 Mon Sep 17 00:00:00 2001 From: Claus Date: Wed, 27 Jan 2016 09:27:56 +0100 Subject: [PATCH 049/406] Ensuring we only do the base SetupNode stuff if its actually a media type and not a folder requested. --- src/umbraco.cms/businesslogic/media/MediaType.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/umbraco.cms/businesslogic/media/MediaType.cs b/src/umbraco.cms/businesslogic/media/MediaType.cs index 2dec2cd56f..308b01c853 100644 --- a/src/umbraco.cms/businesslogic/media/MediaType.cs +++ b/src/umbraco.cms/businesslogic/media/MediaType.cs @@ -161,7 +161,9 @@ namespace umbraco.cms.businesslogic.media protected override void setupNode() { var mediaType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(Id); - SetupNode(mediaType); + // If it's null, it's probably a folder + if (mediaType != null) + SetupNode(mediaType); } #endregion From 02e505698751c6bd3e2f064fed4202aa05be98e3 Mon Sep 17 00:00:00 2001 From: bjarnef Date: Tue, 26 Jan 2016 19:20:19 +0100 Subject: [PATCH 050/406] Change value of vertical-align property. --- src/Umbraco.Web.UI.Client/src/less/components/card.less | 2 +- src/Umbraco.Web.UI.Client/src/less/forms.less | 2 +- src/Umbraco.Web.UI.Client/src/less/installer.less | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/card.less b/src/Umbraco.Web.UI.Client/src/less/components/card.less index 6e1603b3d8..de21406b6a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/card.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/card.less @@ -37,7 +37,7 @@ .umb-card-icons{ text-align: center; - vertical-align: center; + vertical-align: middle; display: block; list-style: none; margin: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index 960ec8a7e2..eb569c7cdf 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -27,7 +27,7 @@ label.control-label { } -.controls-row label{padding: 0 10px 0 10px; vertical-align: center} +.controls-row label{padding: 0 10px 0 10px; vertical-align: middle;} diff --git a/src/Umbraco.Web.UI.Client/src/less/installer.less b/src/Umbraco.Web.UI.Client/src/less/installer.less index 574fde27a9..aa8bbccfef 100644 --- a/src/Umbraco.Web.UI.Client/src/less/installer.less +++ b/src/Umbraco.Web.UI.Client/src/less/installer.less @@ -38,7 +38,7 @@ body { line-height: @baseLineHeight; color: @textColor; - vertical-align: center; + vertical-align: middle; text-align: center; } From 81754b37a4c36d51b17b1e7bef8f376952ee190d Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 18 Jan 2016 17:33:49 +0100 Subject: [PATCH 051/406] Add support for models in controllers --- src/Umbraco.Web/Mvc/RenderModelBinder.cs | 24 ++++++++++++++++++------ src/Umbraco.Web/WebBootManager.cs | 2 +- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web/Mvc/RenderModelBinder.cs b/src/Umbraco.Web/Mvc/RenderModelBinder.cs index 20045604d0..172befb791 100644 --- a/src/Umbraco.Web/Mvc/RenderModelBinder.cs +++ b/src/Umbraco.Web/Mvc/RenderModelBinder.cs @@ -7,8 +7,8 @@ using Umbraco.Web.Models; namespace Umbraco.Web.Mvc { - public class RenderModelBinder : IModelBinder - { + public class RenderModelBinder : IModelBinder, IModelBinderProvider + { /// /// Binds the model to a value by using the specified controller context and binding context. /// @@ -18,14 +18,13 @@ namespace Umbraco.Web.Mvc /// The controller context.The binding context. public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) { - if (bindingContext.ModelType != typeof (RenderModel)) return null; - object model; if (controllerContext.RouteData.DataTokens.TryGetValue("umbraco", out model) == false) return null; - return model as RenderModel; - } + var culture = UmbracoContext.Current.PublishedContentRequest.Culture; + return BindModel(model, bindingContext.ModelType, culture); + } // source is the model that we have // modelType is the type of the model that we need to bind to @@ -103,5 +102,18 @@ namespace Umbraco.Web.Mvc throw new ModelBindingException(string.Format("Cannot bind source type {0} to model type {1}.", sourceType, modelType)); } + + public IModelBinder GetBinder(Type modelType) + { + // can bind to RenderModel + if (modelType == typeof(RenderModel)) return this; + + // can bind to RenderModel + if (modelType.IsGenericType && modelType.GetGenericTypeDefinition() == typeof(RenderModel<>)) return this; + + // can bind to TContent where TContent : IPublishedContent + if (typeof(IPublishedContent).IsAssignableFrom(modelType)) return this; + return null; + } } } \ No newline at end of file diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index d2f7910e5c..1034d33297 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -135,7 +135,7 @@ namespace Umbraco.Web ViewEngines.Engines.Add(new PluginViewEngine()); //set model binder - ModelBinders.Binders.Add(new KeyValuePair(typeof(RenderModel), new RenderModelBinder())); + ModelBinderProviders.BinderProviders.Add(new RenderModelBinder()); // is a provider ////add the profiling action filter //GlobalFilters.Filters.Add(new ProfilingActionFilter()); From e8dfa056b0cae2a338bb5961c2ec3658e4245288 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 13:20:13 +0100 Subject: [PATCH 052/406] U4-2670 Add event to modify the model before being sent to render --- src/Umbraco.Web/Editors/ContentController.cs | 4 +- .../Editors/EditorModelEventManager.cs | 85 +++++++++++++++++++ src/Umbraco.Web/Editors/MediaController.cs | 4 +- src/Umbraco.Web/Editors/MemberController.cs | 2 + .../Models/PublishedContentModels.cs | 12 --- src/Umbraco.Web/Umbraco.Web.csproj | 2 + .../OutgoingEditorModelEventAttribute.cs | 38 +++++++++ 7 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 src/Umbraco.Web/Editors/EditorModelEventManager.cs delete mode 100644 src/Umbraco.Web/Models/PublishedContentModels.cs create mode 100644 src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index cb6e56cb13..6358656d00 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -81,7 +81,8 @@ namespace Umbraco.Web.Editors ///
/// /// - [EnsureUserPermissionForContent("id")] + [OutgoingEditorModelEvent] + [EnsureUserPermissionForContent("id")] public ContentItemDisplay GetById(int id) { var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id)); @@ -116,6 +117,7 @@ namespace Umbraco.Web.Editors /// If this is a container type, we'll remove the umbContainerView tab for a new item since /// it cannot actually list children if it doesn't exist yet. /// + [OutgoingEditorModelEvent] public ContentItemDisplay GetEmpty(string contentTypeAlias, int parentId) { var contentType = Services.ContentTypeService.GetContentType(contentTypeAlias); diff --git a/src/Umbraco.Web/Editors/EditorModelEventManager.cs b/src/Umbraco.Web/Editors/EditorModelEventManager.cs new file mode 100644 index 0000000000..0414bc0cdd --- /dev/null +++ b/src/Umbraco.Web/Editors/EditorModelEventManager.cs @@ -0,0 +1,85 @@ +using System; +using System.Web.Http.Filters; +using Umbraco.Core.Events; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.Editors +{ + public abstract class EditorModelEventArgs : EventArgs + { + protected EditorModelEventArgs(object model, UmbracoContext umbracoContext) + { + Model = model; + UmbracoContext = umbracoContext; + } + + public object Model { get; private set; } + public UmbracoContext UmbracoContext { get; private set; } + } + + public sealed class EditorModelEventArgs : EditorModelEventArgs + { + public EditorModelEventArgs(T model, UmbracoContext umbracoContext) + : base(model, umbracoContext) + { + Model = model; + } + + public new T Model { get; private set; } + } + + /// + /// Used to emit events for editor models in the back office + /// + public sealed class EditorModelEventManager + { + public static event TypedEventHandler> SendingContentModel; + public static event TypedEventHandler> SendingMediaModel; + public static event TypedEventHandler> SendingMemberModel; + + private static void OnSendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingContentModel; + if (handler != null) handler(sender, e); + } + + private static void OnSendingMediaModel(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingMediaModel; + if (handler != null) handler(sender, e); + } + + private static void OnSendingMemberModel(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var handler = SendingMemberModel; + if (handler != null) handler(sender, e); + } + + /// + /// Based on the type, emit's a specific event + /// + /// + /// + internal static void EmitEvent(HttpActionExecutedContext sender, EditorModelEventArgs e) + { + var contentItemDisplay = e.Model as ContentItemDisplay; + if (contentItemDisplay != null) + { + OnSendingContentModel(sender, (EditorModelEventArgs) e); + } + + var mediaItemDisplay = e.Model as MediaItemDisplay; + if (mediaItemDisplay != null) + { + OnSendingMediaModel(sender, (EditorModelEventArgs)e); + } + + var memberItemDisplay = e.Model as MemberDisplay; + if (memberItemDisplay != null) + { + OnSendingMemberModel(sender, (EditorModelEventArgs)e); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index a2637e68e8..e84aaadce5 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -63,13 +63,14 @@ namespace Umbraco.Web.Editors : base(umbracoContext) { } - + /// /// Gets an empty content item for the /// /// /// /// + [OutgoingEditorModelEvent] public MediaItemDisplay GetEmpty(string contentTypeAlias, int parentId) { var contentType = Services.ContentTypeService.GetMediaType(contentTypeAlias); @@ -92,6 +93,7 @@ namespace Umbraco.Web.Editors ///
/// /// + [OutgoingEditorModelEvent] [EnsureUserPermissionForMedia("id")] public MediaItemDisplay GetById(int id) { diff --git a/src/Umbraco.Web/Editors/MemberController.cs b/src/Umbraco.Web/Editors/MemberController.cs index 0788ba43eb..f03cda1bba 100644 --- a/src/Umbraco.Web/Editors/MemberController.cs +++ b/src/Umbraco.Web/Editors/MemberController.cs @@ -145,6 +145,7 @@ namespace Umbraco.Web.Editors /// /// /// + [OutgoingEditorModelEvent] public MemberDisplay GetByKey(Guid key) { MembershipUser foundMembershipMember; @@ -196,6 +197,7 @@ namespace Umbraco.Web.Editors /// /// /// + [OutgoingEditorModelEvent] public MemberDisplay GetEmpty(string contentTypeAlias = null) { IMember emptyContent; diff --git a/src/Umbraco.Web/Models/PublishedContentModels.cs b/src/Umbraco.Web/Models/PublishedContentModels.cs deleted file mode 100644 index d6d3cd9973..0000000000 --- a/src/Umbraco.Web/Models/PublishedContentModels.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Umbraco.Web.PublishedContentModels -{ - class Empty - { - } -} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 2ae1436d58..ac3fb00af2 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -319,6 +319,7 @@ + @@ -686,6 +687,7 @@ + diff --git a/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs new file mode 100644 index 0000000000..3c74f15b8d --- /dev/null +++ b/src/Umbraco.Web/WebApi/Filters/OutgoingEditorModelEventAttribute.cs @@ -0,0 +1,38 @@ +using System; +using System.Net.Http; +using System.Web.Http.Filters; +using Umbraco.Core; +using Umbraco.Web.Editors; +using Umbraco.Web.Models.ContentEditing; + +namespace Umbraco.Web.WebApi.Filters +{ + /// + /// Used to emit outgoing editor model events + /// + internal sealed class OutgoingEditorModelEventAttribute : ActionFilterAttribute + { + public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext) + { + if (actionExecutedContext.Response == null) return; + + var user = UmbracoContext.Current.Security.CurrentUser; + if (user == null) return; + + var objectContent = actionExecutedContext.Response.Content as ObjectContent; + if (objectContent != null) + { + var model = objectContent.Value; + + if (model != null) + { + EditorModelEventManager.EmitEvent(actionExecutedContext, new EditorModelEventArgs( + (dynamic)model, + UmbracoContext.Current)); + } + } + + base.OnActionExecuted(actionExecutedContext); + } + } +} \ No newline at end of file From 9c5d5f431816bb9f96b6868835bdddd7a200a196 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 13:25:54 +0100 Subject: [PATCH 053/406] Fixes: U4-7845 Show drop zone in media section + fix recycle bin for content section + localise texts --- .../listview/layouts/grid/grid.html | 18 +++++++++++------ .../grid/grid.listviewlayout.controller.js | 2 +- .../listview/layouts/list/list.html | 20 +++++++++---------- .../list/list.listviewlayout.controller.js | 2 +- .../umbraco/config/lang/en_us.xml | 1 + 5 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html index f90f607e9d..a2c0ff534e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.html @@ -4,7 +4,7 @@ ng-if="entityType !== 'media'"> - Sorry, we can not find what you are looking for + + + + + @@ -31,7 +37,7 @@ files-uploaded="vm.onUploadComplete" accept="{{vm.acceptedFileTypes}}" max-file-size="{{vm.maxFileSize}}" - hide-dropzone="{{!vm.activeDrag && items.length > 0 || !items && options.filter }}" + hide-dropzone="{{ options.filter.length > 0 }}" compact="{{ items.length > 0 }}" files-queued="vm.onFilesQueue"> @@ -68,9 +74,9 @@ - Sorry, we can not find what you are looking for + diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js index a8a1ac0766..d4ca07aede 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/grid/grid.listviewlayout.controller.js @@ -19,7 +19,7 @@ vm.activeDrag = false; vm.mediaDetailsTooltip = {}; vm.itemsWithoutFolders = []; - vm.isRecycleBin = $scope.contentId === '-21'; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; vm.dragEnter = dragEnter; vm.dragLeave = dragLeave; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html index 24f1dabf33..276274dce3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.html @@ -14,7 +14,7 @@ files-uploaded="vm.onUploadComplete" accept="{{vm.acceptedFileTypes}}" max-file-size="{{vm.maxFileSize}}" - hide-dropzone="{{!vm.activeDrag && items.length > 0 || !items && options.filter }}" + hide-dropzone="{{options.filter.length > 0}}" compact="{{ items.length > 0 }}" files-queued="vm.onFilesQueue"> @@ -31,18 +31,12 @@ on-sort="vm.sort"> - - - -
- Sorry, we can not find what you are looking for + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js index 59327524d6..8a2de97f0e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts/list/list.listviewlayout.controller.js @@ -10,7 +10,7 @@ vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes); vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB"; vm.activeDrag = false; - vm.isRecycleBin = $scope.contentId === '-21'; + vm.isRecycleBin = $scope.contentId === '-21' || $scope.contentId === '-20'; vm.selectItem = selectItem; vm.clickItem = clickItem; diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 258173ce0f..cd0067bf78 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -448,6 +448,7 @@ Retry Permissions Search + Sorry, we can not find what you are looking for Server Show Show page on Send From 38e919357308d39dfe11274ad668fd4737b09972 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 14:18:53 +0100 Subject: [PATCH 054/406] Fixes: U4-7846 Media grid missing min-height on item + make media item sizes as attr so they can be set outside the directive --- .../components/umbmediagrid.directive.js | 47 ++++++++++++++----- .../overlays/mediaPicker/mediapicker.html | 6 ++- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 5175dd3b4d..112e2d9026 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -9,18 +9,36 @@ var itemDefaultWidth = 200; var itemMaxWidth = 200; var itemMaxHeight = 200; + var itemMinWidth = 125; + var itemMinHeight = 125; function activate() { - for (var i = 0; scope.items.length > i; i++) { - var item = scope.items[i]; - setItemData(item); - setOriginalSize(item, itemMaxHeight); - } + if (scope.itemMaxWidth) { + itemMaxWidth = scope.itemMaxWidth; + } - if(scope.items.length > 0) { - setFlexValues(scope.items); - } + if (scope.itemMaxHeight) { + itemMaxHeight = scope.itemMaxHeight; + } + + if (scope.itemMinWidth) { + itemMinWidth = scope.itemMinWidth; + } + + if (scope.itemMinWidth) { + itemMinHeight = scope.itemMinHeight; + } + + for (var i = 0; scope.items.length > i; i++) { + var item = scope.items[i]; + setItemData(item); + setOriginalSize(item, itemMaxHeight); + } + + if (scope.items.length > 0) { + setFlexValues(scope.items); + } } @@ -100,12 +118,13 @@ flex = 1; } - var imageMinWidth = smallestImageWidth * flex; + var imageMinFlexWidth = smallestImageWidth * flex; var flexStyle = { - "flex": flex + " 1 " + imageMinWidth + "px", + "flex": flex + " 1 " + imageMinFlexWidth + "px", "max-width": mediaItem.width + "px", - "min-width": "125px" + "min-width": itemMinWidth + "px", + "min-height": itemMinHeight + "px" }; mediaItem.flexStyle = flexStyle; @@ -154,7 +173,11 @@ onDetailsHover: "=", onClick: '=', onClickName: "=", - filterBy: "=" + filterBy: "=", + itemMaxWidth: "@", + itemMaxHeight: "@", + itemMinWidth: "@", + itemMinHeight: "@" }, link: link }; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html index 0ab9d6dba4..5bcbfbb3d2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/mediaPicker/mediapicker.html @@ -76,7 +76,11 @@ items="images" filter-by="searchTerm" on-click="clickHandler" - on-click-name="clickItemName"> + on-click-name="clickItemName" + item-max-width="150" + item-max-height="150" + item-min-width="100" + item-min-height="100"> From 1c04aa377c417a6a4f9be9d45e387c026acc3c2c Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 14:21:27 +0100 Subject: [PATCH 055/406] fix tab indention --- .../components/umbmediagrid.directive.js | 330 +++++++++--------- 1 file changed, 167 insertions(+), 163 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js index 112e2d9026..eed0cffadb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbmediagrid.directive.js @@ -1,190 +1,194 @@ (function() { - 'use strict'; + 'use strict'; - function MediaGridDirective($filter, mediaHelper) { + function MediaGridDirective($filter, mediaHelper) { - function link(scope, el, attr, ctrl) { + function link(scope, el, attr, ctrl) { - var itemDefaultHeight = 200; - var itemDefaultWidth = 200; - var itemMaxWidth = 200; - var itemMaxHeight = 200; - var itemMinWidth = 125; - var itemMinHeight = 125; + var itemDefaultHeight = 200; + var itemDefaultWidth = 200; + var itemMaxWidth = 200; + var itemMaxHeight = 200; + var itemMinWidth = 125; + var itemMinHeight = 125; - function activate() { + function activate() { - if (scope.itemMaxWidth) { - itemMaxWidth = scope.itemMaxWidth; - } + if (scope.itemMaxWidth) { + itemMaxWidth = scope.itemMaxWidth; + } - if (scope.itemMaxHeight) { - itemMaxHeight = scope.itemMaxHeight; - } + if (scope.itemMaxHeight) { + itemMaxHeight = scope.itemMaxHeight; + } - if (scope.itemMinWidth) { - itemMinWidth = scope.itemMinWidth; - } + if (scope.itemMinWidth) { + itemMinWidth = scope.itemMinWidth; + } - if (scope.itemMinWidth) { - itemMinHeight = scope.itemMinHeight; - } + if (scope.itemMinWidth) { + itemMinHeight = scope.itemMinHeight; + } - for (var i = 0; scope.items.length > i; i++) { - var item = scope.items[i]; - setItemData(item); - setOriginalSize(item, itemMaxHeight); - } + for (var i = 0; scope.items.length > i; i++) { + var item = scope.items[i]; + setItemData(item); + setOriginalSize(item, itemMaxHeight); + } - if (scope.items.length > 0) { - setFlexValues(scope.items); - } - - } - - function setItemData(item) { - item.isFolder = !mediaHelper.hasFilePropertyType(item); - if(!item.isFolder){ - item.thumbnail = mediaHelper.resolveFile(item, true); - item.image = mediaHelper.resolveFile(item, false); - } - } - - function setOriginalSize(item, maxHeight) { - - //set to a square by default - item.width = itemDefaultWidth; - item.height = itemDefaultHeight; - item.aspectRatio = 1; - - var widthProp = _.find(item.properties, function(v) { return (v.alias === "umbracoWidth"); }); - - if (widthProp && widthProp.value) { - item.width = parseInt(widthProp.value, 10); - if (isNaN(item.width)) { - item.width = itemDefaultWidth; - } - } - - var heightProp = _.find(item.properties, function(v) { return (v.alias === "umbracoHeight"); }); - - if (heightProp && heightProp.value) { - item.height = parseInt(heightProp.value, 10); - if (isNaN(item.height)) { - item.height = itemDefaultWidth; - } - } - - item.aspectRatio = item.width / item.height; - - // set max width and height - // landscape - if(item.aspectRatio >= 1) { - if(item.width > itemMaxWidth) { - item.width = itemMaxWidth; - item.height = itemMaxWidth / item.aspectRatio; - } - // portrait - } else { - if(item.height > itemMaxHeight) { - item.height = itemMaxHeight; - item.width = itemMaxHeight * item.aspectRatio; - } - } - - } - - function setFlexValues(mediaItems) { - - var flexSortArray = mediaItems; - var smallestImageWidth = null; - var widestImageAspectRatio = null; - - // sort array after image width with the widest image first - flexSortArray = $filter('orderBy')(flexSortArray, 'width', true); - - // find widest image aspect ratio - widestImageAspectRatio = flexSortArray[0].aspectRatio; - - // find smallest image width - smallestImageWidth = flexSortArray[flexSortArray.length - 1].width; - - for (var i = 0; flexSortArray.length > i; i++) { - - var mediaItem = flexSortArray[i]; - var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio); - - if (flex === 0) { - flex = 1; - } - - var imageMinFlexWidth = smallestImageWidth * flex; - - var flexStyle = { - "flex": flex + " 1 " + imageMinFlexWidth + "px", - "max-width": mediaItem.width + "px", - "min-width": itemMinWidth + "px", - "min-height": itemMinHeight + "px" - }; - - mediaItem.flexStyle = flexStyle; + if (scope.items.length > 0) { + setFlexValues(scope.items); + } } - } - - scope.clickItem = function(item, $event, $index) { - if(scope.onClick) { - scope.onClick(item, $event, $index); + function setItemData(item) { + item.isFolder = !mediaHelper.hasFilePropertyType(item); + if (!item.isFolder) { + item.thumbnail = mediaHelper.resolveFile(item, true); + item.image = mediaHelper.resolveFile(item, false); + } } - }; - scope.clickItemName = function(item, $event, $index) { - if(scope.onClickName) { - scope.onClickName(item, $event, $index); - $event.stopPropagation(); + function setOriginalSize(item, maxHeight) { + + //set to a square by default + item.width = itemDefaultWidth; + item.height = itemDefaultHeight; + item.aspectRatio = 1; + + var widthProp = _.find(item.properties, function(v) { + return (v.alias === "umbracoWidth"); + }); + + if (widthProp && widthProp.value) { + item.width = parseInt(widthProp.value, 10); + if (isNaN(item.width)) { + item.width = itemDefaultWidth; + } + } + + var heightProp = _.find(item.properties, function(v) { + return (v.alias === "umbracoHeight"); + }); + + if (heightProp && heightProp.value) { + item.height = parseInt(heightProp.value, 10); + if (isNaN(item.height)) { + item.height = itemDefaultWidth; + } + } + + item.aspectRatio = item.width / item.height; + + // set max width and height + // landscape + if (item.aspectRatio >= 1) { + if (item.width > itemMaxWidth) { + item.width = itemMaxWidth; + item.height = itemMaxWidth / item.aspectRatio; + } + // portrait + } else { + if (item.height > itemMaxHeight) { + item.height = itemMaxHeight; + item.width = itemMaxHeight * item.aspectRatio; + } + } + } - }; - scope.hoverItemDetails = function(item, $event, hover) { - if(scope.onDetailsHover) { - scope.onDetailsHover(item, $event, hover); + function setFlexValues(mediaItems) { + + var flexSortArray = mediaItems; + var smallestImageWidth = null; + var widestImageAspectRatio = null; + + // sort array after image width with the widest image first + flexSortArray = $filter('orderBy')(flexSortArray, 'width', true); + + // find widest image aspect ratio + widestImageAspectRatio = flexSortArray[0].aspectRatio; + + // find smallest image width + smallestImageWidth = flexSortArray[flexSortArray.length - 1].width; + + for (var i = 0; flexSortArray.length > i; i++) { + + var mediaItem = flexSortArray[i]; + var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio); + + if (flex === 0) { + flex = 1; + } + + var imageMinFlexWidth = smallestImageWidth * flex; + + var flexStyle = { + "flex": flex + " 1 " + imageMinFlexWidth + "px", + "max-width": mediaItem.width + "px", + "min-width": itemMinWidth + "px", + "min-height": itemMinHeight + "px" + }; + + mediaItem.flexStyle = flexStyle; + + } + } - }; - var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue){ - if(angular.isArray(newValue)) { - activate(); - } - }); + scope.clickItem = function(item, $event, $index) { + if (scope.onClick) { + scope.onClick(item, $event, $index); + } + }; - scope.$on('$destroy', function(){ - unbindItemsWatcher(); - }); + scope.clickItemName = function(item, $event, $index) { + if (scope.onClickName) { + scope.onClickName(item, $event, $index); + $event.stopPropagation(); + } + }; - } + scope.hoverItemDetails = function(item, $event, hover) { + if (scope.onDetailsHover) { + scope.onDetailsHover(item, $event, hover); + } + }; - var directive = { - restrict: 'E', - replace: true, - templateUrl: 'views/components/umb-media-grid.html', - scope: { - items: '=', - onDetailsHover: "=", - onClick: '=', - onClickName: "=", - filterBy: "=", - itemMaxWidth: "@", - itemMaxHeight: "@", - itemMinWidth: "@", - itemMinHeight: "@" - }, - link: link - }; + var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue) { + if (angular.isArray(newValue)) { + activate(); + } + }); - return directive; - } + scope.$on('$destroy', function() { + unbindItemsWatcher(); + }); - angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective); + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-media-grid.html', + scope: { + items: '=', + onDetailsHover: "=", + onClick: '=', + onClickName: "=", + filterBy: "=", + itemMaxWidth: "@", + itemMaxHeight: "@", + itemMinWidth: "@", + itemMinHeight: "@" + }, + link: link + }; + + return directive; + } + + angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective); })(); From cc4c7b0d88c667d7592208297918474c9f5f152d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 14:30:57 +0100 Subject: [PATCH 056/406] Adds events for creating/deleting containers --- .../Services/ContentTypeService.cs | 98 +++++++++++++++---- .../Services/IContentTypeService.cs | 9 +- src/Umbraco.Core/Services/OperationStatus.cs | 6 ++ .../Services/OperationStatusType.cs | 7 +- src/Umbraco.Core/Services/PackagingService.cs | 4 +- 5 files changed, 97 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 493768b368..4207149a52 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -44,8 +44,9 @@ namespace Umbraco.Core.Services #region Containers - public Attempt CreateContentTypeContainer(int parentId, string name, int userId = 0) + public Attempt> CreateContentTypeContainer(int parentId, string name, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) { @@ -57,20 +58,29 @@ namespace Umbraco.Core.Services ParentId = parentId, CreatorId = userId }; + + if (SavingContentTypeContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.AddOrUpdate(container); uow.Commit(); - return Attempt.Succeed(container.Id); + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { - return Attempt.Fail(ex); + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } //TODO: Audit trail ? } } - public Attempt CreateMediaTypeContainer(int parentId, string name, int userId = 0) + public Attempt> CreateMediaTypeContainer(int parentId, string name, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) { @@ -82,42 +92,80 @@ namespace Umbraco.Core.Services ParentId = parentId, CreatorId = userId }; + + if (SavingContentTypeContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.AddOrUpdate(container); uow.Commit(); - return Attempt.Succeed(container.Id); + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { - return Attempt.Fail(ex); + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } //TODO: Audit trail ? } } - public void SaveContentTypeContainer(EntityContainer container, int userId = 0) + public Attempt SaveContentTypeContainer(EntityContainer container, int userId = 0) { - SaveContainer(container, Constants.ObjectTypes.DocumentTypeContainerGuid, "document type", userId); + return SaveContainer( + SavingContentTypeContainer, SavedContentTypeContainer, + container, Constants.ObjectTypes.DocumentTypeContainerGuid, "document type", userId); } - public void SaveMediaTypeContainer(EntityContainer container, int userId = 0) + public Attempt SaveMediaTypeContainer(EntityContainer container, int userId = 0) { - SaveContainer(container, Constants.ObjectTypes.MediaTypeContainerGuid, "media type", userId); + return SaveContainer( + SavingMediaTypeContainer, SavedMediaTypeContainer, + container, Constants.ObjectTypes.MediaTypeContainerGuid, "media type", userId); } - private void SaveContainer(EntityContainer container, Guid containerObjectType, string objectTypeName, int userId) + private Attempt SaveContainer( + TypedEventHandler> savingEvent, + TypedEventHandler> savedEvent, + EntityContainer container, + Guid containerObjectType, + string objectTypeName, int userId) { + var evtMsgs = EventMessagesFactory.Get(); + if (container.ContainedObjectType != containerObjectType) - throw new InvalidOperationException("Not a " + objectTypeName + " container."); + { + var ex = new InvalidOperationException("Not a " + objectTypeName + " container."); + return Attempt.Fail(OperationStatus.Exception(evtMsgs, ex), ex); + } + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - throw new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return Attempt.Fail(OperationStatus.Exception(evtMsgs, ex), ex); + } + + if (savingEvent.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + } var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, containerObjectType)) { repo.AddOrUpdate(container); - uow.Commit(); - //TODO: Audit trail ? + uow.Commit(); } + + savedEvent.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + + //TODO: Audit trail ? + + return Attempt.Succeed(OperationStatus.Success(evtMsgs)); } public EntityContainer GetContentTypeContainer(int containerId) @@ -1172,13 +1220,23 @@ namespace Umbraco.Core.Services uow.Commit(); } } - + #region Event Handlers - /// - /// Occurs before Delete - /// - public static event TypedEventHandler> DeletingContentType; + public static event TypedEventHandler> SavingContentTypeContainer; + public static event TypedEventHandler> SavedContentTypeContainer; + public static event TypedEventHandler> DeletingContentTypeContainer; + public static event TypedEventHandler> DeletedContentTypeContainer; + public static event TypedEventHandler> SavingMediaTypeContainer; + public static event TypedEventHandler> SavedMediaTypeContainer; + public static event TypedEventHandler> DeletingMediaTypeContainer; + public static event TypedEventHandler> DeletedMediaTypeContainer; + + + /// + /// Occurs before Delete + /// + public static event TypedEventHandler> DeletingContentType; /// /// Occurs after Delete diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 3c6b8404b5..6f1f4b53b3 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -21,10 +21,11 @@ namespace Umbraco.Core.Services /// Attempt ValidateComposition(IContentTypeComposition compo); - Attempt CreateContentTypeContainer(int parentId, string name, int userId = 0); - Attempt CreateMediaTypeContainer(int parentId, string name, int userId = 0); - void SaveContentTypeContainer(EntityContainer container, int userId = 0); - void SaveMediaTypeContainer(EntityContainer container, int userId = 0); + Attempt> CreateContentTypeContainer(int parentId, string name, int userId = 0); + Attempt> CreateMediaTypeContainer(int parentId, string name, int userId = 0); + Attempt SaveContentTypeContainer(EntityContainer container, int userId = 0); + Attempt SaveMediaTypeContainer(EntityContainer container, int userId = 0); + EntityContainer GetContentTypeContainer(int containerId); EntityContainer GetContentTypeContainer(Guid containerId); IEnumerable GetContentTypeContainers(int[] containerIds); diff --git a/src/Umbraco.Core/Services/OperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus.cs index 31b684c4f1..99ca71d95c 100644 --- a/src/Umbraco.Core/Services/OperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus.cs @@ -45,6 +45,12 @@ namespace Umbraco.Core.Services #region Static Helper methods + internal static OperationStatus Exception(EventMessages eventMessages, Exception ex) + { + eventMessages.Add(new EventMessage("", ex.Message, EventMessageType.Error)); + return new OperationStatus(OperationStatusType.FailedExceptionThrown, eventMessages); + } + internal static OperationStatus Cancelled(EventMessages eventMessages) { return new OperationStatus(OperationStatusType.FailedCancelledByEvent, eventMessages); diff --git a/src/Umbraco.Core/Services/OperationStatusType.cs b/src/Umbraco.Core/Services/OperationStatusType.cs index 14f24c5c4e..ea993e6ff6 100644 --- a/src/Umbraco.Core/Services/OperationStatusType.cs +++ b/src/Umbraco.Core/Services/OperationStatusType.cs @@ -16,7 +16,12 @@ namespace Umbraco.Core.Services /// /// The saving has been cancelled by a 3rd party add-in /// - FailedCancelledByEvent = 14 + FailedCancelledByEvent = 14, + + /// + /// Failed, an exception was thrown/handled + /// + FailedExceptionThrown = 15, //TODO: In the future, we might need to add more operations statuses, potentially like 'FailedByPermissions', etc... } diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 43651dd18b..335da4b600 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -480,7 +480,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + rootFolder, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - var rootFolderId = tryCreateFolder.Result; + var rootFolderId = tryCreateFolder.Result.Entity.Id; current = _contentTypeService.GetContentTypeContainer(rootFolderId); } @@ -514,7 +514,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + folderName, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - return _contentTypeService.GetContentTypeContainer(tryCreateFolder.Result); + return _contentTypeService.GetContentTypeContainer(tryCreateFolder.Result.Entity.Id); } private IContentType CreateContentTypeFromXml(XElement documentType) From dbb7e3825da5ee1f9a83a9d608096c6f033618ff Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 15:22:58 +0100 Subject: [PATCH 057/406] Fixes events for containers --- src/Umbraco.Core/Services/ContentService.cs | 16 ++-- .../Services/ContentTypeService.cs | 54 +++++++++++--- src/Umbraco.Core/Services/DataTypeService.cs | 74 ++++++++++++++++--- src/Umbraco.Core/Services/DomainService.cs | 8 +- .../Services/IContentTypeService.cs | 4 +- src/Umbraco.Core/Services/IDataTypeService.cs | 6 +- src/Umbraco.Core/Services/MediaService.cs | 16 ++-- src/Umbraco.Core/Services/OperationStatus.cs | 17 +++-- .../Services/OperationStatusType.cs | 7 +- src/Umbraco.Core/Services/PackagingService.cs | 4 +- .../Services/PublicAccessService.cs | 12 +-- 11 files changed, 156 insertions(+), 62 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 680692ae4f..439b18d9c6 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -910,7 +910,7 @@ namespace Umbraco.Core.Services new MoveEventArgs(evtMsgs, new MoveEventInfo(content, originalPath, Constants.System.RecycleBinContent)), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var moveInfo = new List> @@ -957,7 +957,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Move, "Move Content to Recycle Bin performed by user", userId, content.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -1080,7 +1080,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(asArray, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } using (new WriteLock(Locker)) @@ -1124,7 +1124,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Bulk Save content performed by user", userId == -1 ? 0 : userId, Constants.System.Root); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -1147,7 +1147,7 @@ namespace Umbraco.Core.Services new DeleteEventArgs(content, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } //Make sure that published content is unpublished before being deleted @@ -1178,7 +1178,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Delete, "Delete Content performed by user", userId, content.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -2043,7 +2043,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(content, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } @@ -2075,7 +2075,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Save Content performed by user", userId, content.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 4207149a52..b30a66b376 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -68,13 +68,16 @@ namespace Umbraco.Core.Services repo.AddOrUpdate(container); uow.Commit(); + + SavedContentTypeContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + //TODO: Audit trail ? + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } - //TODO: Audit trail ? } } @@ -93,7 +96,7 @@ namespace Umbraco.Core.Services CreatorId = userId }; - if (SavingContentTypeContainer.IsRaisedEventCancelled( + if (SavingMediaTypeContainer.IsRaisedEventCancelled( new SaveEventArgs(container, evtMsgs), this)) { @@ -102,13 +105,16 @@ namespace Umbraco.Core.Services repo.AddOrUpdate(container); uow.Commit(); + + SavedMediaTypeContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + //TODO: Audit trail ? + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } - //TODO: Audit trail ? } } @@ -138,20 +144,20 @@ namespace Umbraco.Core.Services if (container.ContainedObjectType != containerObjectType) { var ex = new InvalidOperationException("Not a " + objectTypeName + " container."); - return Attempt.Fail(OperationStatus.Exception(evtMsgs, ex), ex); + return OperationStatus.Exception(evtMsgs, ex); } if (container.HasIdentity && container.IsPropertyDirty("ParentId")) { var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); - return Attempt.Fail(OperationStatus.Exception(evtMsgs, ex), ex); + return OperationStatus.Exception(evtMsgs, ex); } if (savingEvent.IsRaisedEventCancelled( new SaveEventArgs(container, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -165,7 +171,7 @@ namespace Umbraco.Core.Services //TODO: Audit trail ? - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } public EntityContainer GetContentTypeContainer(int containerId) @@ -274,28 +280,54 @@ namespace Umbraco.Core.Services } } - public void DeleteContentTypeContainer(int containerId, int userId = 0) + public Attempt DeleteContentTypeContainer(int containerId, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DocumentTypeContainerGuid)) { var container = repo.Get(containerId); - if (container == null) return; + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (DeletingContentTypeContainer.IsRaisedEventCancelled( + new DeleteEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.Delete(container); uow.Commit(); + + DeletedContentTypeContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + + return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? } } - public void DeleteMediaTypeContainer(int containerId, int userId = 0) + public Attempt DeleteMediaTypeContainer(int containerId, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.MediaTypeContainerGuid)) { var container = repo.Get(containerId); - if (container == null) return; + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (DeletingMediaTypeContainer.IsRaisedEventCancelled( + new DeleteEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.Delete(container); uow.Commit(); + + DeletedMediaTypeContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + + return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? } } diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index a397975f04..035cfd0ab6 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -29,8 +29,9 @@ namespace Umbraco.Core.Services #region Containers - public Attempt CreateContainer(int parentId, string name, int userId = 0) + public Attempt> CreateContainer(int parentId, string name, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) { @@ -42,15 +43,26 @@ namespace Umbraco.Core.Services ParentId = parentId, CreatorId = userId }; + + if (SavingContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(container, OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.AddOrUpdate(container); uow.Commit(); - return Attempt.Succeed(container.Id); + + SavedContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + //TODO: Audit trail ? + + return Attempt.Succeed(new OperationStatus(container, OperationStatusType.Success, evtMsgs)); } catch (Exception ex) { - return Attempt.Fail(ex); + return Attempt.Fail(new OperationStatus(null, OperationStatusType.FailedExceptionThrown, evtMsgs), ex); } - //TODO: Audit trail ? } } @@ -107,31 +119,65 @@ namespace Umbraco.Core.Services } } - public void SaveContainer(EntityContainer container, int userId = 0) + public Attempt SaveContainer(EntityContainer container, int userId = 0) { - if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) - throw new InvalidOperationException("Not a data type container."); + var evtMsgs = EventMessagesFactory.Get(); + + if (container.ContainedObjectType != Constants.ObjectTypes.DataTypeGuid) + { + var ex = new InvalidOperationException("Not a " + Constants.ObjectTypes.DataTypeGuid + " container."); + return OperationStatus.Exception(evtMsgs, ex); + } + if (container.HasIdentity && container.IsPropertyDirty("ParentId")) - throw new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + { + var ex = new InvalidOperationException("Cannot save a container with a modified parent, move the container instead."); + return OperationStatus.Exception(evtMsgs, ex); + } + + if (SavingContainer.IsRaisedEventCancelled( + new SaveEventArgs(container, evtMsgs), + this)) + { + return OperationStatus.Cancelled(evtMsgs); + } var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) { repo.AddOrUpdate(container); uow.Commit(); - //TODO: Audit trail ? } + + SavedContainer.RaiseEvent(new SaveEventArgs(container, evtMsgs), this); + + //TODO: Audit trail ? + + return OperationStatus.Success(evtMsgs); } - public void DeleteContainer(int containerId, int userId = 0) + public Attempt DeleteContainer(int containerId, int userId = 0) { + var evtMsgs = EventMessagesFactory.Get(); var uow = UowProvider.GetUnitOfWork(); using (var repo = RepositoryFactory.CreateEntityContainerRepository(uow, Constants.ObjectTypes.DataTypeContainerGuid)) { var container = repo.Get(containerId); - if (container == null) return; + if (container == null) return OperationStatus.NoOperation(evtMsgs); + + if (DeletingContainer.IsRaisedEventCancelled( + new DeleteEventArgs(container, evtMsgs), + this)) + { + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs)); + } + repo.Delete(container); uow.Commit(); + + DeletedContainer.RaiseEvent(new DeleteEventArgs(container, evtMsgs), this); + + return OperationStatus.Success(evtMsgs); //TODO: Audit trail ? } } @@ -537,6 +583,12 @@ namespace Umbraco.Core.Services } #region Event Handlers + + public static event TypedEventHandler> SavingContainer; + public static event TypedEventHandler> SavedContainer; + public static event TypedEventHandler> DeletingContainer; + public static event TypedEventHandler> DeletedContainer; + /// /// Occurs before Delete /// diff --git a/src/Umbraco.Core/Services/DomainService.cs b/src/Umbraco.Core/Services/DomainService.cs index ca9fe03dcb..3ffcb92778 100644 --- a/src/Umbraco.Core/Services/DomainService.cs +++ b/src/Umbraco.Core/Services/DomainService.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Services new DeleteEventArgs(domain, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -45,7 +45,7 @@ namespace Umbraco.Core.Services var args = new DeleteEventArgs(domain, false, evtMsgs); Deleted.RaiseEvent(args, this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } public IDomain GetByName(string name) @@ -91,7 +91,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(domainEntity, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -102,7 +102,7 @@ namespace Umbraco.Core.Services } Saved.RaiseEvent(new SaveEventArgs(domainEntity, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } #region Event Handlers diff --git a/src/Umbraco.Core/Services/IContentTypeService.cs b/src/Umbraco.Core/Services/IContentTypeService.cs index 6f1f4b53b3..cd905a5ccc 100644 --- a/src/Umbraco.Core/Services/IContentTypeService.cs +++ b/src/Umbraco.Core/Services/IContentTypeService.cs @@ -36,8 +36,8 @@ namespace Umbraco.Core.Services IEnumerable GetMediaTypeContainers(int[] containerIds); IEnumerable GetMediaTypeContainers(string folderName, int level); IEnumerable GetMediaTypeContainers(IMediaType mediaType); - void DeleteMediaTypeContainer(int folderId, int userId = 0); - void DeleteContentTypeContainer(int containerId, int userId = 0); + Attempt DeleteMediaTypeContainer(int folderId, int userId = 0); + Attempt DeleteContentTypeContainer(int containerId, int userId = 0); /// /// Gets all property type aliases. diff --git a/src/Umbraco.Core/Services/IDataTypeService.cs b/src/Umbraco.Core/Services/IDataTypeService.cs index a94628aa87..9e119cd28a 100644 --- a/src/Umbraco.Core/Services/IDataTypeService.cs +++ b/src/Umbraco.Core/Services/IDataTypeService.cs @@ -10,14 +10,14 @@ namespace Umbraco.Core.Services /// public interface IDataTypeService : IService { - Attempt CreateContainer(int parentId, string name, int userId = 0); - void SaveContainer(EntityContainer container, int userId = 0); + Attempt> CreateContainer(int parentId, string name, int userId = 0); + Attempt SaveContainer(EntityContainer container, int userId = 0); EntityContainer GetContainer(int containerId); EntityContainer GetContainer(Guid containerId); IEnumerable GetContainers(string folderName, int level); IEnumerable GetContainers(IDataTypeDefinition dataTypeDefinition); IEnumerable GetContainers(int[] containerIds); - void DeleteContainer(int containerId, int userId = 0); + Attempt DeleteContainer(int containerId, int userId = 0); /// /// Gets a by its Name diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 0416101728..accbebfe0b 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -747,7 +747,7 @@ namespace Umbraco.Core.Services if (Deleting.IsRaisedEventCancelled( new DeleteEventArgs(media, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } //Delete children before deleting the 'possible parent' @@ -772,7 +772,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Delete, "Delete Media performed by user", userId, media.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -791,7 +791,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(media, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } @@ -816,7 +816,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Save Media performed by user", userId, media.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -836,7 +836,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(asArray, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } } @@ -864,7 +864,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Save, "Save Media items performed by user", userId, -1); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -966,7 +966,7 @@ namespace Umbraco.Core.Services if (Trashing.IsRaisedEventCancelled( new MoveEventArgs(new MoveEventInfo(media, originalPath, Constants.System.RecycleBinMedia)), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var moveInfo = new List> @@ -1008,7 +1008,7 @@ namespace Umbraco.Core.Services Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// diff --git a/src/Umbraco.Core/Services/OperationStatus.cs b/src/Umbraco.Core/Services/OperationStatus.cs index 99ca71d95c..1561eacbda 100644 --- a/src/Umbraco.Core/Services/OperationStatus.cs +++ b/src/Umbraco.Core/Services/OperationStatus.cs @@ -45,20 +45,25 @@ namespace Umbraco.Core.Services #region Static Helper methods - internal static OperationStatus Exception(EventMessages eventMessages, Exception ex) + internal static Attempt Exception(EventMessages eventMessages, Exception ex) { eventMessages.Add(new EventMessage("", ex.Message, EventMessageType.Error)); - return new OperationStatus(OperationStatusType.FailedExceptionThrown, eventMessages); + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedExceptionThrown, eventMessages), ex); } - internal static OperationStatus Cancelled(EventMessages eventMessages) + internal static Attempt Cancelled(EventMessages eventMessages) { - return new OperationStatus(OperationStatusType.FailedCancelledByEvent, eventMessages); + return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, eventMessages)); } - internal static OperationStatus Success(EventMessages eventMessages) + internal static Attempt Success(EventMessages eventMessages) { - return new OperationStatus(OperationStatusType.Success, eventMessages); + return Attempt.Succeed(new OperationStatus(OperationStatusType.Success, eventMessages)); + } + + internal static Attempt NoOperation(EventMessages eventMessages) + { + return Attempt.Succeed(new OperationStatus(OperationStatusType.NoOperation, eventMessages)); } #endregion diff --git a/src/Umbraco.Core/Services/OperationStatusType.cs b/src/Umbraco.Core/Services/OperationStatusType.cs index ea993e6ff6..85ec4a4746 100644 --- a/src/Umbraco.Core/Services/OperationStatusType.cs +++ b/src/Umbraco.Core/Services/OperationStatusType.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Services /// The saving was successful. /// Success = 0, - + /// /// The saving has been cancelled by a 3rd party add-in /// @@ -22,6 +22,11 @@ namespace Umbraco.Core.Services /// Failed, an exception was thrown/handled /// FailedExceptionThrown = 15, + + /// + /// When no operation is executed because it was not needed (i.e. deleting an item that doesn't exist) + /// + NoOperation = 100, //TODO: In the future, we might need to add more operations statuses, potentially like 'FailedByPermissions', etc... } diff --git a/src/Umbraco.Core/Services/PackagingService.cs b/src/Umbraco.Core/Services/PackagingService.cs index 335da4b600..9223cad3da 100644 --- a/src/Umbraco.Core/Services/PackagingService.cs +++ b/src/Umbraco.Core/Services/PackagingService.cs @@ -982,7 +982,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + rootFolder, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - current = _dataTypeService.GetContainer(tryCreateFolder.Result); + current = _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); } importedFolders.Add(name, current.Id); @@ -1015,7 +1015,7 @@ namespace Umbraco.Core.Services _logger.Error("Could not create folder: " + folderName, tryCreateFolder.Exception); throw tryCreateFolder.Exception; } - return _dataTypeService.GetContainer(tryCreateFolder.Result); + return _dataTypeService.GetContainer(tryCreateFolder.Result.Entity.Id); } private void SavePrevaluesFromXml(List dataTypes, IEnumerable dataTypeElements) diff --git a/src/Umbraco.Core/Services/PublicAccessService.cs b/src/Umbraco.Core/Services/PublicAccessService.cs index bb966309dc..c2eb536bca 100644 --- a/src/Umbraco.Core/Services/PublicAccessService.cs +++ b/src/Umbraco.Core/Services/PublicAccessService.cs @@ -183,7 +183,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(entry, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } repo.AddOrUpdate(entry); @@ -191,7 +191,7 @@ namespace Umbraco.Core.Services uow.Commit(); Saved.RaiseEvent(new SaveEventArgs(entry, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } } @@ -207,7 +207,7 @@ namespace Umbraco.Core.Services new SaveEventArgs(entry, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -218,7 +218,7 @@ namespace Umbraco.Core.Services } Saved.RaiseEvent(new SaveEventArgs(entry, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// @@ -232,7 +232,7 @@ namespace Umbraco.Core.Services new DeleteEventArgs(entry, evtMsgs), this)) { - return Attempt.Fail(OperationStatus.Cancelled(evtMsgs)); + return OperationStatus.Cancelled(evtMsgs); } var uow = UowProvider.GetUnitOfWork(); @@ -243,7 +243,7 @@ namespace Umbraco.Core.Services } Deleted.RaiseEvent(new DeleteEventArgs(entry, false, evtMsgs), this); - return Attempt.Succeed(OperationStatus.Success(evtMsgs)); + return OperationStatus.Success(evtMsgs); } /// From 98c65ff8f4f83d3db247ad2c01c52edb366cbd85 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 15:29:53 +0100 Subject: [PATCH 058/406] U4-7847 Do not allow folders to be created underneath content types (or data types) --- .../src/views/documenttypes/create.controller.js | 1 + src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 9e4a804010..19a2c7f1d5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -9,6 +9,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, contentTypeResource, formHelper, appState, notificationsService, localizationService) { $scope.model = { + allowCreateFolder: $scope.dialogOptions.currentNode.parentId === null || $scope.dialogOptions.currentNode.nodeType === "container", folderName: "", creatingFolder: false, }; diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index 35b718f7a4..f837e7292b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -20,7 +20,7 @@ -
  • +
  • From bd2b6e0f93a2b73a6837c07eb470e42d81abf874 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 27 Jan 2016 15:30:34 +0100 Subject: [PATCH 059/406] Should hopefully fix unit tests --- .../Persistence/Repositories/TemplateRepositoryTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index 864571d5df..b1b822639c 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = null; -}", template.Content); +}".CrLf(), template.Content); } } @@ -209,7 +209,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = ""test.cshtml""; -}", template2.Content); +}".CrLf(), template2.Content); } } From 4bc51feccd31a8d4354d005009738ef920fc19e8 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 15:31:29 +0100 Subject: [PATCH 060/406] fix semicolons --- .../src/views/documenttypes/create.controller.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 19a2c7f1d5..7f350eda42 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -19,7 +19,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.showCreateFolder = function() { $scope.model.creatingFolder = true; - } + }; $scope.createContainer = function () { if (formHelper.submitForm({ @@ -48,22 +48,22 @@ function DocumentTypesCreateController($scope, $location, navigationService, con } } }); - }; - } + } + }; $scope.createDocType = function() { $location.search('create', null); $location.search('notemplate', null); $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true"); navigationService.hideMenu(); - } + }; $scope.createComponent = function() { $location.search('create', null); $location.search('notemplate', null); $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true"); navigationService.hideMenu(); - } + }; } angular.module('umbraco').controller("Umbraco.Editors.DocumentTypes.CreateController", DocumentTypesCreateController); From b6715956086ae06bf3db53233edeee995cbd68c2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 27 Jan 2016 15:34:15 +0100 Subject: [PATCH 061/406] fix tab indention --- .../views/documenttypes/create.controller.js | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index 7f350eda42..4e734b76a6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -21,19 +21,26 @@ function DocumentTypesCreateController($scope, $location, navigationService, con $scope.model.creatingFolder = true; }; - $scope.createContainer = function () { - if (formHelper.submitForm({ - scope: $scope, - formCtrl: this.createFolderForm, - statusMessage: localizeCreateFolder - })) { - contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { + $scope.createContainer = function() { + + if (formHelper.submitForm({scope: $scope, formCtrl: this.createFolderForm, statusMessage: localizeCreateFolder})) { + + contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function(folderId) { navigationService.hideMenu(); - var currPath = node.path ? node.path : "-1"; - navigationService.syncTree({ tree: "documenttypes", path: currPath + "," + folderId, forceReload: true, activate: true }); - formHelper.resetForm({ scope: $scope }); + var currPath = node.path ? node.path : "-1"; + + navigationService.syncTree({ + tree: "documenttypes", + path: currPath + "," + folderId, + forceReload: true, + activate: true + }); + + formHelper.resetForm({ + scope: $scope + }); var section = appState.getSectionState("currentSection"); @@ -59,11 +66,11 @@ function DocumentTypesCreateController($scope, $location, navigationService, con }; $scope.createComponent = function() { - $location.search('create', null); - $location.search('notemplate', null); - $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true"); - navigationService.hideMenu(); - }; + $location.search('create', null); + $location.search('notemplate', null); + $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true"); + navigationService.hideMenu(); + }; } angular.module('umbraco').controller("Umbraco.Editors.DocumentTypes.CreateController", DocumentTypesCreateController); From 1c58360891fd592a2727ec54bf6aed0baafb17ca Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 27 Jan 2016 15:47:19 +0100 Subject: [PATCH 062/406] Another attempt at fixing unit tests --- .../Persistence/Repositories/TemplateRepositoryTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index b1b822639c..cb8e4680b7 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = null; -}".CrLf(), template.Content); +}".CrLf(), template.Content.CrLf()); } } @@ -209,7 +209,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = ""test.cshtml""; -}".CrLf(), template2.Content); +}".CrLf(), template2.Content.CrLf()); } } From 2913a0f624a2f7585c1a776e7d85b44df78df7f1 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 27 Jan 2016 17:15:30 +0100 Subject: [PATCH 063/406] With only LineFeeds then...? --- .../Persistence/Repositories/TemplateRepositoryTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs index cb8e4680b7..3f168d4741 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/TemplateRepositoryTest.cs @@ -179,7 +179,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = null; -}".CrLf(), template.Content.CrLf()); +}".Lf(), template.Content.Lf()); } } @@ -209,7 +209,7 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.AreEqual(@"@inherits Umbraco.Web.Mvc.UmbracoTemplatePage @{ " + "\t" + @"Layout = ""test.cshtml""; -}".CrLf(), template2.Content.CrLf()); +}".Lf(), template2.Content.Lf()); } } From 02b74a50593be903971a39a1cba5ceee83eb964d Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 18:51:37 +0100 Subject: [PATCH 064/406] Fixes FullDataSetRepositoryCachePolicy so that whenever Ids are requested, it retrieves all of the items and then post filters - ensuring there's no additional trips to the database. Fixes MemberTypeRepository so that calls to PerformGet(int) are wrapping the GetAll() call. --- .../Cache/FullDataSetRepositoryCachePolicy.cs | 14 ++++++ .../Repositories/MemberTypeRepository.cs | 43 ++++--------------- 2 files changed, 22 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index 3b3c98fc80..b435615d54 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -30,6 +30,20 @@ namespace Umbraco.Core.Cache private bool? _hasZeroCountCache; + + public override TEntity[] GetAll(TId[] ids, Func> getFromRepo) + { + //process the base logic without any Ids - we want to cache them all! + var result = base.GetAll(new TId[] { }, getFromRepo); + + //now that the base result has been calculated, they will all be cached. + // Now we can just filter by ids if they have been supplied + + return ids.Any() + ? result.Where(x => ids.Contains((TId) (object) x.Id)).ToArray() + : result; + } + /// /// For this type of caching policy, we don't cache individual items /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 97c744c43e..fd5a19ceab 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -36,26 +36,11 @@ namespace Umbraco.Core.Persistence.Repositories return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); } } - - #region Overrides of RepositoryBase - + protected override IMemberType PerformGet(int id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.NodeId, SqlSyntax); - - var dtos = - Database.Fetch( - new PropertyTypePropertyGroupRelator().Map, sql); - - if (dtos == null || dtos.Any() == false) - return null; - - var factory = new MemberTypeReadOnlyFactory(); - var member = factory.BuildEntity(dtos.First()); - - return member; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -63,10 +48,11 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); if (ids.Any()) { + //NOTE: This logic should never be executed according to our cache policy var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.id='{0}'", x))); sql.Where(statement); } - sql.OrderByDescending(x => x.NodeId); + sql.OrderByDescending(x => x.NodeId, SqlSyntax); var dtos = Database.Fetch( @@ -82,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories var subquery = translator.Translate(); var sql = GetBaseQuery(false) .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)) - .OrderBy(x => x.SortOrder); + .OrderBy(x => x.SortOrder, SqlSyntax); var dtos = Database.Fetch( @@ -90,11 +76,7 @@ namespace Umbraco.Core.Persistence.Repositories return BuildFromDtos(dtos); } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - + protected override Sql GetBaseQuery(bool isCount) { var sql = new Sql(); @@ -168,11 +150,7 @@ namespace Umbraco.Core.Persistence.Repositories { get { return new Guid(Constants.ObjectTypes.MemberType); } } - - #endregion - - #region Unit of Work Implementation - + protected override void PersistNewItem(IMemberType entity) { ValidateAlias(entity); @@ -243,8 +221,6 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } - - #endregion /// /// Override so we can specify explicit db type's on any property types that are built-in. @@ -282,9 +258,6 @@ namespace Umbraco.Core.Persistence.Repositories else { return GetAll(); - //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - //var allIds = Database.Fetch(sql).ToArray(); - //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } From 0cefdcbc20ab9c910a80f7db02431b7abd4c3267 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 18:55:10 +0100 Subject: [PATCH 065/406] Fixes ContentTypeBaseRepository - there were several hack in place to query by GUID but this is totally irrelavent because we are caching them all so the result should be from the GetAll(), so this removes all of those hacks which greatly simplifies things. This fixes the MapContentTypes and MapMediaTypes methods - before this was looking up a collection and then recursing back to Get(int) (but we already have all of the data we need), then it was storing a lot of arrays in memory to build up an Entity when it's much faster to just iterate forward only and build up the entity. This saves 5 SQL calls on startup. --- src/Umbraco.Core/Models/ContentType.cs | 6 + .../Repositories/ContentTypeBaseRepository.cs | 531 ++++++++---------- .../Repositories/ContentTypeRepository.cs | 15 +- .../Repositories/MediaTypeRepository.cs | 22 +- .../Repositories/RepositoryBase.cs | 3 +- .../Querying/ContentTypeSqlMappingTests.cs | 12 +- 6 files changed, 265 insertions(+), 324 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index b6021e6538..0926e48e31 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -53,6 +53,9 @@ namespace Umbraco.Core.Models /// /// Gets or sets the alias of the default Template. + /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// we should not store direct entity /// [IgnoreDataMember] public ITemplate DefaultTemplate @@ -79,6 +82,9 @@ namespace Umbraco.Core.Models /// /// Gets or Sets a list of Templates which are allowed for the ContentType + /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// we should not store direct entity /// [DataMember] public IEnumerable AllowedTemplates diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index eb8dfde6b0..066c914bb4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -584,14 +584,13 @@ AND umbracoNode.id <> @id", } } - public static IEnumerable GetMediaTypes( - TId[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, + public static IEnumerable GetMediaTypes( + Database db, ISqlSyntaxProvider sqlSyntax, TRepo contentTypeRepository) - where TRepo : IReadRepository - where TId: struct + where TRepo : IReadRepository { - IDictionary> allParentMediaTypeIds; - var mediaTypes = MapMediaTypes(mediaTypeIds, db, sqlSyntax, out allParentMediaTypeIds) + IDictionary> allParentMediaTypeIds; + var mediaTypes = MapMediaTypes(db, sqlSyntax, out allParentMediaTypeIds) .ToArray(); MapContentTypeChildren(mediaTypes, db, sqlSyntax, contentTypeRepository, allParentMediaTypeIds); @@ -599,16 +598,15 @@ AND umbracoNode.id <> @id", return mediaTypes; } - public static IEnumerable GetContentTypes( - TId[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, + public static IEnumerable GetContentTypes( + Database db, ISqlSyntaxProvider sqlSyntax, TRepo contentTypeRepository, ITemplateRepository templateRepository) - where TRepo : IReadRepository - where TId : struct + where TRepo : IReadRepository { - IDictionary> allAssociatedTemplates; - IDictionary> allParentContentTypeIds; - var contentTypes = MapContentTypes(contentTypeIds, db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) + IDictionary> allAssociatedTemplates; + IDictionary> allParentContentTypeIds; + var contentTypes = MapContentTypes(db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) .ToArray(); if (contentTypes.Any()) @@ -623,12 +621,11 @@ AND umbracoNode.id <> @id", return contentTypes; } - internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes, + internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes, Database db, ISqlSyntaxProvider sqlSyntax, TRepo contentTypeRepository, - IDictionary> allParentContentTypeIds) - where TRepo : IReadRepository - where TId : struct + IDictionary> allParentContentTypeIds) + where TRepo : IReadRepository { //NOTE: SQL call #2 @@ -650,20 +647,20 @@ AND umbracoNode.id <> @id", var allParentIdsAsArray = allParentContentTypeIds.SelectMany(x => x.Value).Distinct().ToArray(); if (allParentIdsAsArray.Any()) { - var allParentContentTypes = contentTypeRepository.GetAll(allParentIdsAsArray).ToArray(); + //NO!!!!!!!!!!! Do not recurse lookup, we've already looked them all up + //var allParentContentTypes = contentTypeRepository.GetAll(allParentIdsAsArray).ToArray(); + + var allParentContentTypes = contentTypes.Where(x => allParentIdsAsArray.Contains(x.Id)).ToArray(); + foreach (var contentType in contentTypes) { - //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids - // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be - var entityId = typeof(TId) == typeof(int) ? contentType.Id : (object)contentType.Key; + var entityId = contentType.Id; var parentContentTypes = allParentContentTypes.Where(x => - { - //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids - // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be - var parentEntityId = typeof(TId) == typeof(int) ? x.Id : (object)x.Key; + { + var parentEntityId = x.Id; - return allParentContentTypeIds[(TId)entityId].Contains((TId)parentEntityId); + return allParentContentTypeIds[entityId].Contains(parentEntityId); }); foreach (var parentContentType in parentContentTypes) { @@ -681,13 +678,12 @@ AND umbracoNode.id <> @id", } - internal static void MapContentTypeTemplates(IContentType[] contentTypes, + internal static void MapContentTypeTemplates(IContentType[] contentTypes, Database db, TRepo contentTypeRepository, ITemplateRepository templateRepository, - IDictionary> associatedTemplates) - where TRepo : IReadRepository - where TId: struct + IDictionary> associatedTemplates) + where TRepo : IReadRepository { if (associatedTemplates == null || associatedTemplates.Any() == false) return; @@ -704,11 +700,9 @@ AND umbracoNode.id <> @id", foreach (var contentType in contentTypes) { - //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids - // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be - var entityId = typeof(TId) == typeof(int) ? contentType.Id : (object)contentType.Key; + var entityId = contentType.Id; - var associatedTemplateIds = associatedTemplates[(TId)entityId].Select(x => x.TemplateId) + var associatedTemplateIds = associatedTemplates[entityId].Select(x => x.TemplateId) .Distinct() .ToArray(); @@ -720,15 +714,10 @@ AND umbracoNode.id <> @id", } - internal static IEnumerable MapMediaTypes(TId[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> parentMediaTypeIds) - where TId : struct - { - Mandate.That(mediaTypeIds.Any(), () => new InvalidOperationException("must be at least one content type id specified")); - Mandate.ParameterNotNull(db, "db"); - - //ensure they are unique - mediaTypeIds = mediaTypeIds.Distinct().ToArray(); + internal static IEnumerable MapMediaTypes(Database db, ISqlSyntaxProvider sqlSyntax, + out IDictionary> parentMediaTypeIds) + { + Mandate.ParameterNotNull(db, "db"); var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, @@ -754,27 +743,10 @@ AND umbracoNode.id <> @id", ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" ) ParentTypes ON ParentTypes.childContentTypeId = cmsContentType.nodeId - WHERE (umbracoNode.nodeObjectType = @nodeObjectType)"; - - if (mediaTypeIds.Any()) - { - //TODO: This is all sorts of hacky but i don't have time to refactor a lot to get both ints and guids working nicely... this will - // work for the time being. - if (typeof(TId) == typeof(int)) - { - sql = sql + " AND (umbracoNode.id IN (@contentTypeIds))"; - } - else if (typeof(TId) == typeof(Guid)) - { - sql = sql + " AND (umbracoNode.uniqueID IN (@contentTypeIds))"; - } - } - - //NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count! - if ((mediaTypeIds.Length - 1) > 2000) - throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters"); - - var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.MediaType), contentTypeIds = mediaTypeIds }); + WHERE (umbracoNode.nodeObjectType = @nodeObjectType) + ORDER BY ctId"; + + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.MediaType) }); if (result.Any() == false) { @@ -782,87 +754,109 @@ AND umbracoNode.id <> @id", return Enumerable.Empty(); } - parentMediaTypeIds = new Dictionary>(); + parentMediaTypeIds = new Dictionary>(); var mappedMediaTypes = new List(); - foreach (var contentTypeId in mediaTypeIds) + //loop through each result and fill in our required values, each row will contain different requried data than the rest. + // it is much quicker to iterate each result and populate instead of looking up the values over and over in the result like + // we used to do. + var queue = new Queue(result); + var currAllowedContentTypes = new List(); + while (queue.Count > 0) { - //the current content type id that we're working with - - var currentCtId = contentTypeId; - - //first we want to get the main content type data this is 1 : 1 with umbraco node data - - var ct = result - .Where(x => - { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof (TId) == typeof (int)) - ? x.ctId == currentCtId - : x.nUniqueId == currentCtId; - }) - .Select(x => new { x.ctPk, x.ctId, x.ctAlias, x.ctAllowAtRoot, x.ctDesc, x.ctIcon, x.ctIsContainer, x.ctThumb, x.nName, x.nCreateDate, x.nLevel, x.nObjectType, x.nUser, x.nParentId, x.nPath, x.nSortOrder, x.nTrashed, x.nUniqueId }) - .DistinctBy(x => (int)x.ctId) - .FirstOrDefault(); - - if (ct == null) + var ct = queue.Dequeue(); + + //check for allowed content types + int? allowedCtId = ct.ctaAllowedId; + int? allowedCtSort = ct.ctaSortOrder; + string allowedCtAlias = ct.ctaAlias; + if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null) { - continue; + var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias); + if (currAllowedContentTypes.Contains(ctSort) == false) + { + currAllowedContentTypes.Add(ctSort); + } } - var contentTypeDto = new ContentTypeDto + //always ensure there's a list for this content type + if (parentMediaTypeIds.ContainsKey(ct.ctId) == false) + parentMediaTypeIds[ct.ctId] = new List(); + + //check for parent ids and assign to the outgoing collection + int? parentId = ct.chtParentId; + if (parentId.HasValue) { - Alias = ct.ctAlias, - AllowAtRoot = ct.ctAllowAtRoot, - Description = ct.ctDesc, - Icon = ct.ctIcon, - IsContainer = ct.ctIsContainer, - NodeId = ct.ctId, - PrimaryKey = ct.ctPk, - Thumbnail = ct.ctThumb, - //map the underlying node dto - NodeDto = new NodeDto - { - CreateDate = ct.nCreateDate, - Level = (short)ct.nLevel, - NodeId = ct.ctId, - NodeObjectType = ct.nObjectType, - ParentId = ct.nParentId, - Path = ct.nPath, - SortOrder = ct.nSortOrder, - Text = ct.nName, - Trashed = ct.nTrashed, - UniqueId = ct.nUniqueId, - UserId = ct.nUser - } - }; + var associatedParentIds = parentMediaTypeIds[ct.ctId]; + if (associatedParentIds.Contains(parentId.Value) == false) + associatedParentIds.Add(parentId.Value); + } - //now create the media type object + if (queue.Count == 0 || queue.Peek().ctId != ct.ctId) + { + //it's the last in the queue or the content type is changing (moving to the next one) + var mediaType = CreateForMapping(ct, currAllowedContentTypes); + mappedMediaTypes.Add(mediaType); - var factory = new ContentTypeFactory(); - var mediaType = factory.BuildMediaTypeEntity(contentTypeDto); - - //map the allowed content types - //map the child content type ids - MapCommonContentTypeObjects(mediaType, currentCtId, result, parentMediaTypeIds); - - mappedMediaTypes.Add(mediaType); - } + //Here we need to reset the current variables, we're now collecting data for a different content type + currAllowedContentTypes = new List(); + } + } return mappedMediaTypes; } - internal static IEnumerable MapContentTypes(TId[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> associatedTemplates, - out IDictionary> parentContentTypeIds) - where TId : struct + private static IMediaType CreateForMapping(dynamic currCt, List currAllowedContentTypes) + { + // * create the DTO object + // * create the content type object + // * map the allowed content types + // * add to the outgoing list + + var contentTypeDto = new ContentTypeDto + { + Alias = currCt.ctAlias, + AllowAtRoot = currCt.ctAllowAtRoot, + Description = currCt.ctDesc, + Icon = currCt.ctIcon, + IsContainer = currCt.ctIsContainer, + NodeId = currCt.ctId, + PrimaryKey = currCt.ctPk, + Thumbnail = currCt.ctThumb, + //map the underlying node dto + NodeDto = new NodeDto + { + CreateDate = currCt.nCreateDate, + Level = (short)currCt.nLevel, + NodeId = currCt.ctId, + NodeObjectType = currCt.nObjectType, + ParentId = currCt.nParentId, + Path = currCt.nPath, + SortOrder = currCt.nSortOrder, + Text = currCt.nName, + Trashed = currCt.nTrashed, + UniqueId = currCt.nUniqueId, + UserId = currCt.nUser + } + }; + + //now create the content type object + + var factory = new ContentTypeFactory(); + var mediaType = factory.BuildMediaTypeEntity(contentTypeDto); + + //map the allowed content types + mediaType.AllowedContentTypes = currAllowedContentTypes; + + return mediaType; + } + + internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, + out IDictionary> associatedTemplates, + out IDictionary> parentContentTypeIds) { Mandate.ParameterNotNull(db, "db"); - - //ensure they are unique - contentTypeIds = contentTypeIds.Distinct().ToArray(); - + var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, @@ -897,28 +891,10 @@ AND umbracoNode.id <> @id", ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" ) ParentTypes ON ParentTypes.childContentTypeId = cmsContentType.nodeId - WHERE (umbracoNode.nodeObjectType = @nodeObjectType)"; - - if (contentTypeIds.Any()) - { - //TODO: This is all sorts of hacky but i don't have time to refactor a lot to get both ints and guids working nicely... this will - // work for the time being. - if (typeof(TId) == typeof(int)) - { - sql = sql + " AND (umbracoNode.id IN (@contentTypeIds))"; - } - else if (typeof(TId) == typeof(Guid)) - { - sql = sql + " AND (umbracoNode.uniqueID IN (@contentTypeIds))"; - } - } - - - //NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count! - if ((contentTypeIds.Length - 1) > 2000) - throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters"); - - var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType), contentTypeIds = contentTypeIds }); + WHERE (umbracoNode.nodeObjectType = @nodeObjectType) + ORDER BY ctId"; + + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType)}); if (result.Any() == false) { @@ -927,170 +903,139 @@ AND umbracoNode.id <> @id", return Enumerable.Empty(); } - parentContentTypeIds = new Dictionary>(); - associatedTemplates = new Dictionary>(); + parentContentTypeIds = new Dictionary>(); + associatedTemplates = new Dictionary>(); var mappedContentTypes = new List(); - foreach (var contentTypeId in contentTypeIds) + var queue = new Queue(result); + var currDefaultTemplate = -1; + var currAllowedContentTypes = new List(); + while (queue.Count > 0) { - //the current content type id that we're working with + var ct = queue.Dequeue(); - var currentCtId = contentTypeId; - - //first we want to get the main content type data this is 1 : 1 with umbraco node data - - var ct = result - .Where(x => - { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof(TId) == typeof(int)) - ? x.ctId == currentCtId - : x.nUniqueId == currentCtId; - }) - .Select(x => new { x.ctPk, x.ctId, x.ctAlias, x.ctAllowAtRoot, x.ctDesc, x.ctIcon, x.ctIsContainer, x.ctThumb, x.nName, x.nCreateDate, x.nLevel, x.nObjectType, x.nUser, x.nParentId, x.nPath, x.nSortOrder, x.nTrashed, x.nUniqueId }) - .DistinctBy(x => (int)x.ctId) - .FirstOrDefault(); - - if (ct == null) + //check for default templates + bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault); + int? templateId = ct.dtTemplateId; + if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && templateId.HasValue) { - continue; + currDefaultTemplate = templateId.Value; } - //get the unique list of associated templates - var defaultTemplates = result - .Where(x => - { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof(TId) == typeof(int)) - ? x.ctId == currentCtId - : x.nUniqueId == currentCtId; - }) - //use a tuple so that distinct checks both values (in some rare cases the dtIsDefault will not compute as bool?, so we force it with Convert.ToBoolean) - .Select(x => new Tuple(Convert.ToBoolean(x.dtIsDefault), x.dtTemplateId)) - .Where(x => x.Item1.HasValue && x.Item2.HasValue) - .Distinct() - .OrderByDescending(x => x.Item1.Value) - .ToArray(); - //if there isn't one set to default explicitly, we'll pick the first one - var defaultTemplate = defaultTemplates.FirstOrDefault(x => x.Item1.Value) - ?? defaultTemplates.FirstOrDefault(); + //always ensure there's a list for this content type + if (associatedTemplates.ContainsKey(ct.ctId) == false) + associatedTemplates[ct.ctId] = new List(); - var dtDto = new ContentTypeTemplateDto + //check for associated templates and assign to the outgoing collection + if (ct.tId != null) { - //create the content type dto - ContentTypeDto = new ContentTypeDto + var associatedTemplate = new AssociatedTemplate(ct.tId, ct.tAlias, ct.tText); + var associatedList = associatedTemplates[ct.ctId]; + + if (associatedList.Contains(associatedTemplate) == false) + associatedList.Add(associatedTemplate); + } + + //check for allowed content types + int? allowedCtId = ct.ctaAllowedId; + int? allowedCtSort = ct.ctaSortOrder; + string allowedCtAlias = ct.ctaAlias; + if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null) + { + var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias); + if (currAllowedContentTypes.Contains(ctSort) == false) { - Alias = ct.ctAlias, - AllowAtRoot = ct.ctAllowAtRoot, - Description = ct.ctDesc, - Icon = ct.ctIcon, - IsContainer = ct.ctIsContainer, - NodeId = ct.ctId, - PrimaryKey = ct.ctPk, - Thumbnail = ct.ctThumb, - //map the underlying node dto - NodeDto = new NodeDto - { - CreateDate = ct.nCreateDate, - Level = (short)ct.nLevel, - NodeId = ct.ctId, - NodeObjectType = ct.nObjectType, - ParentId = ct.nParentId, - Path = ct.nPath, - SortOrder = ct.nSortOrder, - Text = ct.nName, - Trashed = ct.nTrashed, - UniqueId = ct.nUniqueId, - UserId = ct.nUser - } - }, - ContentTypeNodeId = ct.ctId, - IsDefault = defaultTemplate != null, - TemplateNodeId = defaultTemplate != null ? defaultTemplate.Item2.Value : 0, - }; + currAllowedContentTypes.Add(ctSort); + } + } - // We will map a subset of the associated template - alias, id, name + //always ensure there's a list for this content type + if (parentContentTypeIds.ContainsKey(ct.ctId) == false) + parentContentTypeIds[ct.ctId] = new List(); - associatedTemplates.Add(currentCtId, result - .Where(x => - { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof(TId) == typeof(int)) - ? x.ctId == currentCtId - : x.nUniqueId == currentCtId; - }) - .Where(x => x.tId != null) - .Select(x => new AssociatedTemplate(x.tId, x.tAlias, x.tText)) - .Distinct() - .ToArray()); + //check for parent ids and assign to the outgoing collection + int? parentId = ct.chtParentId; + if (parentId.HasValue) + { + var associatedParentIds = parentContentTypeIds[ct.ctId]; - //now create the content type object + if (associatedParentIds.Contains(parentId.Value) == false) + associatedParentIds.Add(parentId.Value); + } - var factory = new ContentTypeFactory(); - var contentType = factory.BuildContentTypeEntity(dtDto.ContentTypeDto); - - // NOTE - // that was done by the factory but makes little sense, moved here, so - // now we have to reset dirty props again (as the factory does it) and yet, - // we are not managing allowed templates... the whole thing is weird. - ((ContentType) contentType).DefaultTemplateId = dtDto.TemplateNodeId; - contentType.ResetDirtyProperties(false); + if (queue.Count == 0 || queue.Peek().ctId != ct.ctId) + { + //it's the last in the queue or the content type is changing (moving to the next one) + var contentType = CreateForMapping(ct, currAllowedContentTypes, currDefaultTemplate); + mappedContentTypes.Add(contentType); - //map the allowed content types - //map the child content type ids - MapCommonContentTypeObjects(contentType, currentCtId, result, parentContentTypeIds); - - mappedContentTypes.Add(contentType); + //Here we need to reset the current variables, we're now collecting data for a different content type + currDefaultTemplate = -1; + currAllowedContentTypes = new List(); + } } return mappedContentTypes; } - private static void MapCommonContentTypeObjects(T contentType, TId currentCtId, List result, IDictionary> parentContentTypeIds) - where T : IContentTypeBase - where TId : struct + private static IContentType CreateForMapping(dynamic currCt, List currAllowedContentTypes, int currDefaultTemplate) { - //map the allowed content types - contentType.AllowedContentTypes = result - .Where(x => - { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof(TId) == typeof(int)) - ? x.ctId == currentCtId - : x.nUniqueId == currentCtId; - }) - //use tuple so we can use distinct on all vals - .Select(x => new Tuple(x.ctaAllowedId, x.ctaSortOrder, x.ctaAlias)) - .Where(x => x.Item1.HasValue && x.Item2.HasValue && x.Item3 != null) - .Distinct() - .Select(x => new ContentTypeSort(new Lazy(() => x.Item1.Value), x.Item2.Value, x.Item3)) - .ToList(); + // * set the default template to the first one if a default isn't found + // * create the DTO object + // * create the content type object + // * map the allowed content types + // * add to the outgoing list - //map the child content type ids - parentContentTypeIds.Add(currentCtId, result - .Where(x => + var dtDto = new ContentTypeTemplateDto + { + //create the content type dto + ContentTypeDto = new ContentTypeDto { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof(TId) == typeof(int)) - ? x.ctId == currentCtId - : x.nUniqueId == currentCtId; - }) - .Select(x => - { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof(TId) == typeof(int)) - ? (TId?)x.chtParentId - : (TId?)x.chtParentKey; - }) - .Where(x => x.HasValue) - .Distinct() - .Select(x => x.Value).ToList()); + Alias = currCt.ctAlias, + AllowAtRoot = currCt.ctAllowAtRoot, + Description = currCt.ctDesc, + Icon = currCt.ctIcon, + IsContainer = currCt.ctIsContainer, + NodeId = currCt.ctId, + PrimaryKey = currCt.ctPk, + Thumbnail = currCt.ctThumb, + //map the underlying node dto + NodeDto = new NodeDto + { + CreateDate = currCt.nCreateDate, + Level = (short)currCt.nLevel, + NodeId = currCt.ctId, + NodeObjectType = currCt.nObjectType, + ParentId = currCt.nParentId, + Path = currCt.nPath, + SortOrder = currCt.nSortOrder, + Text = currCt.nName, + Trashed = currCt.nTrashed, + UniqueId = currCt.nUniqueId, + UserId = currCt.nUser + } + }, + ContentTypeNodeId = currCt.ctId, + IsDefault = currDefaultTemplate != -1, + TemplateNodeId = currDefaultTemplate != -1 ? currDefaultTemplate : 0, + }; + + //now create the content type object + + var factory = new ContentTypeFactory(); + var contentType = factory.BuildContentTypeEntity(dtDto.ContentTypeDto); + + // NOTE + // that was done by the factory but makes little sense, moved here, so + // now we have to reset dirty props again (as the factory does it) and yet, + // we are not managing allowed templates... the whole thing is weird. + ((ContentType)contentType).DefaultTemplateId = dtDto.TemplateNodeId; + contentType.ResetDirtyProperties(false); + + //map the allowed content types + contentType.AllowedContentTypes = currAllowedContentTypes; + + return contentType; } internal static void MapGroupsAndProperties(int[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index da6b0a6efb..910d5c41ea 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -51,14 +51,12 @@ namespace Umbraco.Core.Persistence.Repositories { if (ids.Any()) { - return ContentTypeQueryMapper.GetContentTypes(ids, Database, SqlSyntax, this, _templateRepository); - } - else - { - var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - var allIds = Database.Fetch(sql).ToArray(); - return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); + //NOTE: This logic should never be executed according to our cache policy + return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, this, _templateRepository) + .Where(x => ids.Contains(x.Id)); } + + return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, this, _templateRepository); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -300,9 +298,6 @@ namespace Umbraco.Core.Persistence.Repositories else { return GetAll(); - //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - //var allIds = Database.Fetch(sql).ToArray(); - //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 225aa759d1..bb6090951d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -40,25 +40,20 @@ namespace Umbraco.Core.Persistence.Repositories protected override IMediaType PerformGet(int id) { - var contentTypes = ContentTypeQueryMapper.GetMediaTypes( - new[] { id }, Database, SqlSyntax, this); - - var contentType = contentTypes.SingleOrDefault(); - return contentType; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) { if (ids.Any()) { - return ContentTypeQueryMapper.GetMediaTypes(ids, Database, SqlSyntax, this); - } - else - { - var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - var allIds = Database.Fetch(sql).ToArray(); - return ContentTypeQueryMapper.GetMediaTypes(allIds, Database, SqlSyntax, this); + //NOTE: This logic should never be executed according to our cache policy + return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, this) + .Where(x => ids.Contains(x.Id)); } + + return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, this); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -178,9 +173,6 @@ namespace Umbraco.Core.Persistence.Repositories else { return GetAll(); - //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - //var allIds = Database.Fetch(sql).ToArray(); - //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index f6bcc46d06..88b8e93772 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -179,7 +179,8 @@ namespace Umbraco.Core.Persistence.Repositories using (var p = CachePolicyFactory.CreatePolicy()) { - return p.GetAll(ids, PerformGetAll); + var result = p.GetAll(ids, PerformGetAll); + return result; } } diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs index caf872a995..68de27ba6d 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs @@ -55,10 +55,11 @@ namespace Umbraco.Tests.Persistence.Querying transaction.Complete(); } - IDictionary> allAssociatedTemplates; - IDictionary> allParentContentTypeIds; + IDictionary> allAssociatedTemplates; + IDictionary> allParentContentTypeIds; var contentTypes = ContentTypeRepository.ContentTypeQueryMapper.MapContentTypes( - new[] {99997, 99998}, DatabaseContext.Database, SqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) + DatabaseContext.Database, SqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) + .Where(x => (new[] {99997, 99998}).Contains(x.Id)) .ToArray(); var contentType1 = contentTypes.SingleOrDefault(x => x.Id == 99997); @@ -109,9 +110,10 @@ namespace Umbraco.Tests.Persistence.Querying transaction.Complete(); } - IDictionary> allParentContentTypeIds; + IDictionary> allParentContentTypeIds; var contentTypes = ContentTypeRepository.ContentTypeQueryMapper.MapMediaTypes( - new[] { 99997, 99998 }, DatabaseContext.Database, SqlSyntax, out allParentContentTypeIds) + DatabaseContext.Database, SqlSyntax, out allParentContentTypeIds) + .Where(x => (new[] { 99997, 99998 }).Contains(x.Id)) .ToArray(); var contentType1 = contentTypes.SingleOrDefault(x => x.Id == 99997); From 5d965346786e9055257c66cc1813b53f077dc8c0 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 19:20:24 +0100 Subject: [PATCH 066/406] fixes tests --- .../Cache/FullDataSetRepositoryCachePolicy.cs | 7 +++++-- .../Cache/FullDataSetRepositoryCachePolicyFactory.cs | 11 +++++++---- .../Persistence/Repositories/ContentTypeRepository.cs | 2 +- .../Persistence/Repositories/DomainRepository.cs | 2 +- .../Persistence/Repositories/LanguageRepository.cs | 2 +- .../Persistence/Repositories/MediaTypeRepository.cs | 2 +- .../Persistence/Repositories/MemberTypeRepository.cs | 2 +- .../Repositories/PublicAccessRepository.cs | 2 +- .../Persistence/Repositories/RepositoryBase.cs | 6 +++++- .../Persistence/Repositories/TemplateRepository.cs | 2 +- .../Cache/FullDataSetCachePolicyTests.cs | 8 ++++---- 11 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index b435615d54..c098af8992 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -18,7 +18,9 @@ namespace Umbraco.Core.Cache internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IAggregateRoot { - public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, + private readonly Func _getEntityId; + + public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func getEntityId) : base(cache, new RepositoryCachePolicyOptions { //Definitely allow zero'd cache entires since this is a full set, in many cases there will be none, @@ -26,6 +28,7 @@ namespace Umbraco.Core.Cache GetAllCacheAllowZeroCount = true }) { + _getEntityId = getEntityId; } private bool? _hasZeroCountCache; @@ -40,7 +43,7 @@ namespace Umbraco.Core.Cache // Now we can just filter by ids if they have been supplied return ids.Any() - ? result.Where(x => ids.Contains((TId) (object) x.Id)).ToArray() + ? result.Where(x => ids.Contains(_getEntityId(x))).ToArray() : result; } diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs index 75bdae7e83..6a79c2b8c2 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs @@ -1,3 +1,4 @@ +using System; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Cache @@ -11,15 +12,17 @@ namespace Umbraco.Core.Cache where TEntity : class, IAggregateRoot { private readonly IRuntimeCacheProvider _runtimeCache; - - public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache) + private readonly Func _getEntityId; + + public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, Func getEntityId) { - _runtimeCache = runtimeCache; + _runtimeCache = runtimeCache; + _getEntityId = getEntityId; } public virtual IRepositoryCachePolicy CreatePolicy() { - return new FullDataSetRepositoryCachePolicy(_runtimeCache); + return new FullDataSetRepositoryCachePolicy(_runtimeCache, _getEntityId); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 910d5c41ea..1c67aa0cf5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -37,7 +37,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs index 6abab73dd7..563243f12c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index 6fc9bd5ebc..3884eac888 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index bb6090951d..86c0927664 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index fd5a19ceab..581ca3c4a6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index 1086b9cee0..1d8e56190b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 88b8e93772..5534a9ea40 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -82,7 +82,11 @@ namespace Umbraco.Core.Persistence.Repositories { } - + + protected virtual TId GetEntityId(TEntity entity) + { + return (TId)(object)entity.Id; + } /// /// The runtime cache used for this repo by default is the isolated cache for this type diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index acc17b370a..3545bc1c55 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -51,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs index 3d3884c686..9187fe5b27 100644 --- a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Cache return cached.Any() ? new DeepCloneableList() : null; }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); using (defaultPolicy) { var found = defaultPolicy.GetAll(new object[] {}, o => new AuditItem[] {}); @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Cache Assert.IsNotNull(list); //Do it again, ensure that its coming from the cache! - defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); using (defaultPolicy) { var found = defaultPolicy.GetAll(new object[] { }, o => new AuditItem[] { }); @@ -73,7 +73,7 @@ namespace Umbraco.Tests.Cache }); cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem[] { }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); using (defaultPolicy) { var found = defaultPolicy.GetAll(new object[] { }, o => new[] @@ -98,7 +98,7 @@ namespace Umbraco.Tests.Cache new AuditItem(2, "blah2", AuditType.Copy, 123) }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); using (defaultPolicy) { var found = defaultPolicy.GetAll(new object[] { }, o => new[] { (AuditItem)null }); From 6c5e09fd84efb7989d21d42038c81650d6315b0a Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 19:26:08 +0100 Subject: [PATCH 067/406] Ensures that the main content type repositories are returning the correct result from their GetAll methods. Fixes the FullDataSetRepositoryCachePolicy to ensure that any request with Ids actually returns the result of GetAll and then filters to ensure caching is correct. --- .../Cache/FullDataSetRepositoryCachePolicy.cs | 14 ++++++ .../Repositories/ContentTypeRepository.cs | 15 +++---- .../Repositories/MediaTypeRepository.cs | 22 +++------- .../Repositories/MemberTypeRepository.cs | 44 ++++--------------- .../Repositories/RepositoryBase.cs | 3 +- 5 files changed, 37 insertions(+), 61 deletions(-) diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index 3b3c98fc80..b435615d54 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -30,6 +30,20 @@ namespace Umbraco.Core.Cache private bool? _hasZeroCountCache; + + public override TEntity[] GetAll(TId[] ids, Func> getFromRepo) + { + //process the base logic without any Ids - we want to cache them all! + var result = base.GetAll(new TId[] { }, getFromRepo); + + //now that the base result has been calculated, they will all be cached. + // Now we can just filter by ids if they have been supplied + + return ids.Any() + ? result.Where(x => ids.Contains((TId) (object) x.Id)).ToArray() + : result; + } + /// /// For this type of caching policy, we don't cache individual items /// diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 8ab98e86e4..2fdc0e8613 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -48,14 +48,12 @@ namespace Umbraco.Core.Persistence.Repositories { if (ids.Any()) { - return ContentTypeQueryMapper.GetContentTypes(ids, Database, SqlSyntax, this, _templateRepository); - } - else - { - var sql = new Sql().Select("id").From().Where(dto => dto.NodeObjectType == NodeObjectTypeId); - var allIds = Database.Fetch(sql).ToArray(); - return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); + //NOTE: This logic should never be executed according to our cache policy + return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, this, _templateRepository) + .Where(x => ids.Contains(x.Id)); } + + return ContentTypeQueryMapper.GetContentTypes(Database, SqlSyntax, this, _templateRepository); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -269,9 +267,6 @@ namespace Umbraco.Core.Persistence.Repositories else { return GetAll(); - //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - //var allIds = Database.Fetch(sql).ToArray(); - //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 68f7a3c0d9..9147cead55 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -37,25 +37,20 @@ namespace Umbraco.Core.Persistence.Repositories protected override IMediaType PerformGet(int id) { - var contentTypes = ContentTypeQueryMapper.GetMediaTypes( - new[] { id }, Database, SqlSyntax, this); - - var contentType = contentTypes.SingleOrDefault(); - return contentType; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) { if (ids.Any()) { - return ContentTypeQueryMapper.GetMediaTypes(ids, Database, SqlSyntax, this); - } - else - { - var sql = new Sql().Select("id").From().Where(dto => dto.NodeObjectType == NodeObjectTypeId); - var allIds = Database.Fetch(sql).ToArray(); - return ContentTypeQueryMapper.GetMediaTypes(allIds, Database, SqlSyntax, this); + //NOTE: This logic should never be executed according to our cache policy + return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, this) + .Where(x => ids.Contains(x.Id)); } + + return ContentTypeQueryMapper.GetMediaTypes(Database, SqlSyntax, this); } protected override IEnumerable PerformGetByQuery(IQuery query) @@ -190,9 +185,6 @@ namespace Umbraco.Core.Persistence.Repositories else { return GetAll(); - //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - //var allIds = Database.Fetch(sql).ToArray(); - //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index b864c05a83..ca2b0715ac 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -36,26 +36,11 @@ namespace Umbraco.Core.Persistence.Repositories return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); } } - - #region Overrides of RepositoryBase - + protected override IMemberType PerformGet(int id) { - var sql = GetBaseQuery(false); - sql.Where(GetBaseWhereClause(), new { Id = id }); - sql.OrderByDescending(x => x.NodeId); - - var dtos = - Database.Fetch( - new PropertyTypePropertyGroupRelator().Map, sql); - - if (dtos == null || dtos.Any() == false) - return null; - - var factory = new MemberTypeReadOnlyFactory(); - var member = factory.BuildEntity(dtos.First()); - - return member; + //use the underlying GetAll which will force cache all content types + return GetAll().FirstOrDefault(x => x.Id == id); } protected override IEnumerable PerformGetAll(params int[] ids) @@ -63,10 +48,11 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); if (ids.Any()) { + //NOTE: This logic should never be executed according to our cache policy var statement = string.Join(" OR ", ids.Select(x => string.Format("umbracoNode.id='{0}'", x))); sql.Where(statement); } - sql.OrderByDescending(x => x.NodeId); + sql.OrderByDescending(x => x.NodeId, SqlSyntax); var dtos = Database.Fetch( @@ -82,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories var subquery = translator.Translate(); var sql = GetBaseQuery(false) .Append(new Sql("WHERE umbracoNode.id IN (" + subquery.SQL + ")", subquery.Arguments)) - .OrderBy(x => x.SortOrder); + .OrderBy(x => x.SortOrder, SqlSyntax); var dtos = Database.Fetch( @@ -90,11 +76,7 @@ namespace Umbraco.Core.Persistence.Repositories return BuildFromDtos(dtos); } - - #endregion - - #region Overrides of PetaPocoRepositoryBase - + protected override Sql GetBaseQuery(bool isCount) { var sql = new Sql(); @@ -168,11 +150,7 @@ namespace Umbraco.Core.Persistence.Repositories { get { return new Guid(Constants.ObjectTypes.MemberType); } } - - #endregion - - #region Unit of Work Implementation - + protected override void PersistNewItem(IMemberType entity) { ValidateAlias(entity); @@ -249,8 +227,7 @@ namespace Umbraco.Core.Persistence.Repositories entity.ResetDirtyProperties(); } - - #endregion + /// /// Override so we can specify explicit db type's on any property types that are built-in. @@ -288,9 +265,6 @@ namespace Umbraco.Core.Persistence.Repositories else { return GetAll(); - //var sql = new Sql().Select("id").From(SqlSyntax).Where(dto => dto.NodeObjectType == NodeObjectTypeId); - //var allIds = Database.Fetch(sql).ToArray(); - //return ContentTypeQueryMapper.GetContentTypes(allIds, Database, SqlSyntax, this, _templateRepository); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index f6bcc46d06..88b8e93772 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -179,7 +179,8 @@ namespace Umbraco.Core.Persistence.Repositories using (var p = CachePolicyFactory.CreatePolicy()) { - return p.GetAll(ids, PerformGetAll); + var result = p.GetAll(ids, PerformGetAll); + return result; } } From 7d9ba0e36e7c662795e5c292d9e14492771f51de Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 19:42:06 +0100 Subject: [PATCH 068/406] manually backports changes from 7.4 fixes --- src/Umbraco.Core/Models/ContentType.cs | 6 + .../Repositories/ContentTypeBaseRepository.cs | 484 +++++++++--------- .../Querying/ContentTypeSqlMappingTests.cs | 12 +- 3 files changed, 252 insertions(+), 250 deletions(-) diff --git a/src/Umbraco.Core/Models/ContentType.cs b/src/Umbraco.Core/Models/ContentType.cs index 355d724fbe..6a13ea48f5 100644 --- a/src/Umbraco.Core/Models/ContentType.cs +++ b/src/Umbraco.Core/Models/ContentType.cs @@ -53,6 +53,9 @@ namespace Umbraco.Core.Models /// /// Gets or sets the alias of the default Template. + /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// we should not store direct entity /// [IgnoreDataMember] public ITemplate DefaultTemplate @@ -79,6 +82,9 @@ namespace Umbraco.Core.Models /// /// Gets or Sets a list of Templates which are allowed for the ContentType + /// TODO: This should be ignored from cloning!!!!!!!!!!!!!! + /// - but to do that we have to implement callback hacks, this needs to be fixed in v8, + /// we should not store direct entity /// [DataMember] public IEnumerable AllowedTemplates diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 2f42919ce3..58a31d7c36 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -562,14 +562,13 @@ AND umbracoNode.id <> @id", } } - public static IEnumerable GetMediaTypes( - TId[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, + public static IEnumerable GetMediaTypes( + Database db, ISqlSyntaxProvider sqlSyntax, TRepo contentTypeRepository) - where TRepo : IReadRepository - where TId: struct + where TRepo : IReadRepository { - IDictionary> allParentMediaTypeIds; - var mediaTypes = MapMediaTypes(mediaTypeIds, db, sqlSyntax, out allParentMediaTypeIds) + IDictionary> allParentMediaTypeIds; + var mediaTypes = MapMediaTypes(db, sqlSyntax, out allParentMediaTypeIds) .ToArray(); MapContentTypeChildren(mediaTypes, db, sqlSyntax, contentTypeRepository, allParentMediaTypeIds); @@ -577,16 +576,15 @@ AND umbracoNode.id <> @id", return mediaTypes; } - public static IEnumerable GetContentTypes( - TId[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, + public static IEnumerable GetContentTypes( + Database db, ISqlSyntaxProvider sqlSyntax, TRepo contentTypeRepository, ITemplateRepository templateRepository) - where TRepo : IReadRepository - where TId : struct + where TRepo : IReadRepository { - IDictionary> allAssociatedTemplates; - IDictionary> allParentContentTypeIds; - var contentTypes = MapContentTypes(contentTypeIds, db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) + IDictionary> allAssociatedTemplates; + IDictionary> allParentContentTypeIds; + var contentTypes = MapContentTypes(db, sqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) .ToArray(); if (contentTypes.Any()) @@ -601,12 +599,11 @@ AND umbracoNode.id <> @id", return contentTypes; } - internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes, + internal static void MapContentTypeChildren(IContentTypeComposition[] contentTypes, Database db, ISqlSyntaxProvider sqlSyntax, TRepo contentTypeRepository, - IDictionary> allParentContentTypeIds) - where TRepo : IReadRepository - where TId : struct + IDictionary> allParentContentTypeIds) + where TRepo : IReadRepository { //NOTE: SQL call #2 @@ -628,20 +625,17 @@ AND umbracoNode.id <> @id", var allParentIdsAsArray = allParentContentTypeIds.SelectMany(x => x.Value).Distinct().ToArray(); if (allParentIdsAsArray.Any()) { - var allParentContentTypes = contentTypeRepository.GetAll(allParentIdsAsArray).ToArray(); + var allParentContentTypes = contentTypes.Where(x => allParentIdsAsArray.Contains(x.Id)).ToArray(); + foreach (var contentType in contentTypes) - { - //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids - // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be - var entityId = typeof(TId) == typeof(int) ? contentType.Id : (object)contentType.Key; + { + var entityId = contentType.Id; var parentContentTypes = allParentContentTypes.Where(x => { - //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids - // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be - var parentEntityId = typeof(TId) == typeof(int) ? x.Id : (object)x.Key; + var parentEntityId = x.Id; - return allParentContentTypeIds[(TId)entityId].Contains((TId)parentEntityId); + return allParentContentTypeIds[entityId].Contains(parentEntityId); }); foreach (var parentContentType in parentContentTypes) { @@ -659,13 +653,12 @@ AND umbracoNode.id <> @id", } - internal static void MapContentTypeTemplates(IContentType[] contentTypes, + internal static void MapContentTypeTemplates(IContentType[] contentTypes, Database db, TRepo contentTypeRepository, ITemplateRepository templateRepository, - IDictionary> associatedTemplates) - where TRepo : IReadRepository - where TId: struct + IDictionary> associatedTemplates) + where TRepo : IReadRepository { if (associatedTemplates == null || associatedTemplates.Any() == false) return; @@ -682,11 +675,9 @@ AND umbracoNode.id <> @id", foreach (var contentType in contentTypes) { - //TODO: this is pretty hacky right now but i don't have time to refactor/fix running queries based on ints and Guids - // (i.e. for v8) but we need queries by GUIDs now so this is how it's gonna have to be - var entityId = typeof(TId) == typeof(int) ? contentType.Id : (object)contentType.Key; - - var associatedTemplateIds = associatedTemplates[(TId)entityId].Select(x => x.TemplateId) + var entityId = contentType.Id; + + var associatedTemplateIds = associatedTemplates[entityId].Select(x => x.TemplateId) .Distinct() .ToArray(); @@ -698,16 +689,11 @@ AND umbracoNode.id <> @id", } - internal static IEnumerable MapMediaTypes(TId[] mediaTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> parentMediaTypeIds) - where TId : struct + internal static IEnumerable MapMediaTypes(Database db, ISqlSyntaxProvider sqlSyntax, + out IDictionary> parentMediaTypeIds) { - Mandate.That(mediaTypeIds.Any(), () => new InvalidOperationException("must be at least one content type id specified")); Mandate.ParameterNotNull(db, "db"); - - //ensure they are unique - mediaTypeIds = mediaTypeIds.Distinct().ToArray(); - + var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, @@ -732,27 +718,10 @@ AND umbracoNode.id <> @id", ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" ) ParentTypes ON ParentTypes.childContentTypeId = cmsContentType.nodeId - WHERE (umbracoNode.nodeObjectType = @nodeObjectType)"; - - if (mediaTypeIds.Any()) - { - //TODO: This is all sorts of hacky but i don't have time to refactor a lot to get both ints and guids working nicely... this will - // work for the time being. - if (typeof(TId) == typeof(int)) - { - sql = sql + " AND (umbracoNode.id IN (@contentTypeIds))"; - } - else if (typeof(TId) == typeof(Guid)) - { - sql = sql + " AND (umbracoNode.uniqueID IN (@contentTypeIds))"; - } - } - - //NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count! - if ((mediaTypeIds.Length - 1) > 2000) - throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters"); - - var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.MediaType), contentTypeIds = mediaTypeIds }); + WHERE (umbracoNode.nodeObjectType = @nodeObjectType) + ORDER BY ctId"; + + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.MediaType) }); if (result.Any() == false) { @@ -760,87 +729,110 @@ AND umbracoNode.id <> @id", return Enumerable.Empty(); } - parentMediaTypeIds = new Dictionary>(); + parentMediaTypeIds = new Dictionary>(); var mappedMediaTypes = new List(); - foreach (var contentTypeId in mediaTypeIds) + //loop through each result and fill in our required values, each row will contain different requried data than the rest. + // it is much quicker to iterate each result and populate instead of looking up the values over and over in the result like + // we used to do. + var queue = new Queue(result); + var currAllowedContentTypes = new List(); + + while (queue.Count > 0) { - //the current content type id that we're working with + var ct = queue.Dequeue(); - var currentCtId = contentTypeId; - - //first we want to get the main content type data this is 1 : 1 with umbraco node data - - var ct = result - .Where(x => - { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof (TId) == typeof (int)) - ? x.ctId == currentCtId - : x.nUniqueId == currentCtId; - }) - .Select(x => new { x.ctPk, x.ctId, x.ctAlias, x.ctAllowAtRoot, x.ctDesc, x.ctIcon, x.ctIsContainer, x.ctThumb, x.nName, x.nCreateDate, x.nLevel, x.nObjectType, x.nUser, x.nParentId, x.nPath, x.nSortOrder, x.nTrashed, x.nUniqueId }) - .DistinctBy(x => (int)x.ctId) - .FirstOrDefault(); - - if (ct == null) + //check for allowed content types + int? allowedCtId = ct.ctaAllowedId; + int? allowedCtSort = ct.ctaSortOrder; + string allowedCtAlias = ct.ctaAlias; + if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null) { - continue; + var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias); + if (currAllowedContentTypes.Contains(ctSort) == false) + { + currAllowedContentTypes.Add(ctSort); + } } - var contentTypeDto = new ContentTypeDto + //always ensure there's a list for this content type + if (parentMediaTypeIds.ContainsKey(ct.ctId) == false) + parentMediaTypeIds[ct.ctId] = new List(); + + //check for parent ids and assign to the outgoing collection + int? parentId = ct.chtParentId; + if (parentId.HasValue) { - Alias = ct.ctAlias, - AllowAtRoot = ct.ctAllowAtRoot, - Description = ct.ctDesc, - Icon = ct.ctIcon, - IsContainer = ct.ctIsContainer, - NodeId = ct.ctId, - PrimaryKey = ct.ctPk, - Thumbnail = ct.ctThumb, - //map the underlying node dto - NodeDto = new NodeDto - { - CreateDate = ct.nCreateDate, - Level = (short)ct.nLevel, - NodeId = ct.ctId, - NodeObjectType = ct.nObjectType, - ParentId = ct.nParentId, - Path = ct.nPath, - SortOrder = ct.nSortOrder, - Text = ct.nName, - Trashed = ct.nTrashed, - UniqueId = ct.nUniqueId, - UserId = ct.nUser - } - }; + var associatedParentIds = parentMediaTypeIds[ct.ctId]; + if (associatedParentIds.Contains(parentId.Value) == false) + associatedParentIds.Add(parentId.Value); + } - //now create the media type object + if (queue.Count == 0 || queue.Peek().ctId != ct.ctId) + { + //it's the last in the queue or the content type is changing (moving to the next one) + var mediaType = CreateForMapping(ct, currAllowedContentTypes); + mappedMediaTypes.Add(mediaType); - var factory = new MediaTypeFactory(new Guid(Constants.ObjectTypes.MediaType)); - var mediaType = factory.BuildEntity(contentTypeDto); - - //map the allowed content types - //map the child content type ids - MapCommonContentTypeObjects(mediaType, currentCtId, result, parentMediaTypeIds); - - mappedMediaTypes.Add(mediaType); + //Here we need to reset the current variables, we're now collecting data for a different content type + currAllowedContentTypes = new List(); + } } return mappedMediaTypes; } - internal static IEnumerable MapContentTypes(TId[] contentTypeIds, Database db, ISqlSyntaxProvider sqlSyntax, - out IDictionary> associatedTemplates, - out IDictionary> parentContentTypeIds) - where TId : struct + private static IMediaType CreateForMapping(dynamic currCt, List currAllowedContentTypes) + { + // * create the DTO object + // * create the content type object + // * map the allowed content types + // * add to the outgoing list + + var contentTypeDto = new ContentTypeDto + { + Alias = currCt.ctAlias, + AllowAtRoot = currCt.ctAllowAtRoot, + Description = currCt.ctDesc, + Icon = currCt.ctIcon, + IsContainer = currCt.ctIsContainer, + NodeId = currCt.ctId, + PrimaryKey = currCt.ctPk, + Thumbnail = currCt.ctThumb, + //map the underlying node dto + NodeDto = new NodeDto + { + CreateDate = currCt.nCreateDate, + Level = (short)currCt.nLevel, + NodeId = currCt.ctId, + NodeObjectType = currCt.nObjectType, + ParentId = currCt.nParentId, + Path = currCt.nPath, + SortOrder = currCt.nSortOrder, + Text = currCt.nName, + Trashed = currCt.nTrashed, + UniqueId = currCt.nUniqueId, + UserId = currCt.nUser + } + }; + + //now create the content type object + + var factory = new MediaTypeFactory(new Guid(Constants.ObjectTypes.MediaType)); + var mediaType = factory.BuildEntity(contentTypeDto); + + //map the allowed content types + mediaType.AllowedContentTypes = currAllowedContentTypes; + + return mediaType; + } + + internal static IEnumerable MapContentTypes(Database db, ISqlSyntaxProvider sqlSyntax, + out IDictionary> associatedTemplates, + out IDictionary> parentContentTypeIds) { Mandate.ParameterNotNull(db, "db"); - - //ensure they are unique - contentTypeIds = contentTypeIds.Distinct().ToArray(); - + var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, @@ -875,28 +867,10 @@ AND umbracoNode.id <> @id", ON cmsContentType2ContentType.parentContentTypeId = umbracoNode." + sqlSyntax.GetQuotedColumnName("id") + @" ) ParentTypes ON ParentTypes.childContentTypeId = cmsContentType.nodeId - WHERE (umbracoNode.nodeObjectType = @nodeObjectType)"; - - if (contentTypeIds.Any()) - { - //TODO: This is all sorts of hacky but i don't have time to refactor a lot to get both ints and guids working nicely... this will - // work for the time being. - if (typeof(TId) == typeof(int)) - { - sql = sql + " AND (umbracoNode.id IN (@contentTypeIds))"; - } - else if (typeof(TId) == typeof(Guid)) - { - sql = sql + " AND (umbracoNode.uniqueID IN (@contentTypeIds))"; - } - } - - - //NOTE: we are going to assume there's not going to be more than 2100 content type ids since that is the max SQL param count! - if ((contentTypeIds.Length - 1) > 2000) - throw new InvalidOperationException("Cannot perform this lookup, too many sql parameters"); - - var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType), contentTypeIds = contentTypeIds }); + WHERE (umbracoNode.nodeObjectType = @nodeObjectType) + ORDER BY ctId"; + + var result = db.Fetch(sql, new { nodeObjectType = new Guid(Constants.ObjectTypes.DocumentType)}); if (result.Any() == false) { @@ -905,121 +879,141 @@ AND umbracoNode.id <> @id", return Enumerable.Empty(); } - parentContentTypeIds = new Dictionary>(); - associatedTemplates = new Dictionary>(); + parentContentTypeIds = new Dictionary>(); + associatedTemplates = new Dictionary>(); var mappedContentTypes = new List(); - foreach (var contentTypeId in contentTypeIds) + var queue = new Queue(result); + var currDefaultTemplate = -1; + var currAllowedContentTypes = new List(); + while (queue.Count > 0) { - //the current content type id that we're working with + var ct = queue.Dequeue(); - var currentCtId = contentTypeId; - - //first we want to get the main content type data this is 1 : 1 with umbraco node data - - var ct = result - .Where(x => - { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof(TId) == typeof(int)) - ? x.ctId == currentCtId - : x.nUniqueId == currentCtId; - }) - .Select(x => new { x.ctPk, x.ctId, x.ctAlias, x.ctAllowAtRoot, x.ctDesc, x.ctIcon, x.ctIsContainer, x.ctThumb, x.nName, x.nCreateDate, x.nLevel, x.nObjectType, x.nUser, x.nParentId, x.nPath, x.nSortOrder, x.nTrashed, x.nUniqueId }) - .DistinctBy(x => (int)x.ctId) - .FirstOrDefault(); - - if (ct == null) + //check for default templates + bool? isDefaultTemplate = Convert.ToBoolean(ct.dtIsDefault); + int? templateId = ct.dtTemplateId; + if (currDefaultTemplate == -1 && isDefaultTemplate.HasValue && templateId.HasValue) { - continue; + currDefaultTemplate = templateId.Value; } - //get the unique list of associated templates - var defaultTemplates = result - .Where(x => - { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof(TId) == typeof(int)) - ? x.ctId == currentCtId - : x.nUniqueId == currentCtId; - }) - //use a tuple so that distinct checks both values (in some rare cases the dtIsDefault will not compute as bool?, so we force it with Convert.ToBoolean) - .Select(x => new Tuple(Convert.ToBoolean(x.dtIsDefault), x.dtTemplateId)) - .Where(x => x.Item1.HasValue && x.Item2.HasValue) - .Distinct() - .OrderByDescending(x => x.Item1.Value) - .ToArray(); - //if there isn't one set to default explicitly, we'll pick the first one - var defaultTemplate = defaultTemplates.FirstOrDefault(x => x.Item1.Value) - ?? defaultTemplates.FirstOrDefault(); + //always ensure there's a list for this content type + if (associatedTemplates.ContainsKey(ct.ctId) == false) + associatedTemplates[ct.ctId] = new List(); - var dtDto = new DocumentTypeDto + //check for associated templates and assign to the outgoing collection + if (ct.tId != null) { - //create the content type dto - ContentTypeDto = new ContentTypeDto + var associatedTemplate = new AssociatedTemplate(ct.tId, ct.tAlias, ct.tText); + var associatedList = associatedTemplates[ct.ctId]; + + if (associatedList.Contains(associatedTemplate) == false) + associatedList.Add(associatedTemplate); + } + + //check for allowed content types + int? allowedCtId = ct.ctaAllowedId; + int? allowedCtSort = ct.ctaSortOrder; + string allowedCtAlias = ct.ctaAlias; + if (allowedCtId.HasValue && allowedCtSort.HasValue && allowedCtAlias != null) + { + var ctSort = new ContentTypeSort(new Lazy(() => allowedCtId.Value), allowedCtSort.Value, allowedCtAlias); + if (currAllowedContentTypes.Contains(ctSort) == false) { - Alias = ct.ctAlias, - AllowAtRoot = ct.ctAllowAtRoot, - Description = ct.ctDesc, - Icon = ct.ctIcon, - IsContainer = ct.ctIsContainer, - NodeId = ct.ctId, - PrimaryKey = ct.ctPk, - Thumbnail = ct.ctThumb, - //map the underlying node dto - NodeDto = new NodeDto - { - CreateDate = ct.nCreateDate, - Level = (short)ct.nLevel, - NodeId = ct.ctId, - NodeObjectType = ct.nObjectType, - ParentId = ct.nParentId, - Path = ct.nPath, - SortOrder = ct.nSortOrder, - Text = ct.nName, - Trashed = ct.nTrashed, - UniqueId = ct.nUniqueId, - UserId = ct.nUser - } - }, - ContentTypeNodeId = ct.ctId, - IsDefault = defaultTemplate != null, - TemplateNodeId = defaultTemplate != null ? defaultTemplate.Item2.Value : 0, - }; + currAllowedContentTypes.Add(ctSort); + } + } - // We will map a subset of the associated template - alias, id, name + //always ensure there's a list for this content type + if (parentContentTypeIds.ContainsKey(ct.ctId) == false) + parentContentTypeIds[ct.ctId] = new List(); - associatedTemplates.Add(currentCtId, result - .Where(x => - { - //TODO: This is a bit hacky right now but don't have time to do a nice refactor to support both GUID and Int queries, so this is - // how it is for now. - return (typeof(TId) == typeof(int)) - ? x.ctId == currentCtId - : x.nUniqueId == currentCtId; - }) - .Where(x => x.tId != null) - .Select(x => new AssociatedTemplate(x.tId, x.tAlias, x.tText)) - .Distinct() - .ToArray()); + //check for parent ids and assign to the outgoing collection + int? parentId = ct.chtParentId; + if (parentId.HasValue) + { + var associatedParentIds = parentContentTypeIds[ct.ctId]; - //now create the content type object + if (associatedParentIds.Contains(parentId.Value) == false) + associatedParentIds.Add(parentId.Value); + } - var factory = new ContentTypeFactory(new Guid(Constants.ObjectTypes.DocumentType)); - var contentType = factory.BuildEntity(dtDto); + if (queue.Count == 0 || queue.Peek().ctId != ct.ctId) + { + //it's the last in the queue or the content type is changing (moving to the next one) + var contentType = CreateForMapping(ct, currAllowedContentTypes, currDefaultTemplate); + mappedContentTypes.Add(contentType); - //map the allowed content types - //map the child content type ids - MapCommonContentTypeObjects(contentType, currentCtId, result, parentContentTypeIds); - - mappedContentTypes.Add(contentType); + //Here we need to reset the current variables, we're now collecting data for a different content type + currDefaultTemplate = -1; + currAllowedContentTypes = new List(); + } } return mappedContentTypes; } + private static IContentType CreateForMapping(dynamic currCt, List currAllowedContentTypes, int currDefaultTemplate) + { + // * set the default template to the first one if a default isn't found + // * create the DTO object + // * create the content type object + // * map the allowed content types + // * add to the outgoing list + + var dtDto = new DocumentTypeDto + { + //create the content type dto + ContentTypeDto = new ContentTypeDto + { + Alias = currCt.ctAlias, + AllowAtRoot = currCt.ctAllowAtRoot, + Description = currCt.ctDesc, + Icon = currCt.ctIcon, + IsContainer = currCt.ctIsContainer, + NodeId = currCt.ctId, + PrimaryKey = currCt.ctPk, + Thumbnail = currCt.ctThumb, + //map the underlying node dto + NodeDto = new NodeDto + { + CreateDate = currCt.nCreateDate, + Level = (short)currCt.nLevel, + NodeId = currCt.ctId, + NodeObjectType = currCt.nObjectType, + ParentId = currCt.nParentId, + Path = currCt.nPath, + SortOrder = currCt.nSortOrder, + Text = currCt.nName, + Trashed = currCt.nTrashed, + UniqueId = currCt.nUniqueId, + UserId = currCt.nUser + } + }, + ContentTypeNodeId = currCt.ctId, + IsDefault = currDefaultTemplate != -1, + TemplateNodeId = currDefaultTemplate != -1 ? currDefaultTemplate : 0, + }; + + //now create the content type object + + var factory = new ContentTypeFactory(new Guid(Constants.ObjectTypes.DocumentType)); + var contentType = factory.BuildEntity(dtDto); + + // NOTE + // that was done by the factory but makes little sense, moved here, so + // now we have to reset dirty props again (as the factory does it) and yet, + // we are not managing allowed templates... the whole thing is weird. + ((ContentType)contentType).DefaultTemplateId = dtDto.TemplateNodeId; + contentType.ResetDirtyProperties(false); + + //map the allowed content types + contentType.AllowedContentTypes = currAllowedContentTypes; + + return contentType; + } + private static void MapCommonContentTypeObjects(T contentType, TId currentCtId, List result, IDictionary> parentContentTypeIds) where T : IContentTypeBase where TId : struct diff --git a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs index f63eea7c88..2608f84ae9 100644 --- a/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs +++ b/src/Umbraco.Tests/Persistence/Querying/ContentTypeSqlMappingTests.cs @@ -55,10 +55,11 @@ namespace Umbraco.Tests.Persistence.Querying transaction.Complete(); } - IDictionary> allAssociatedTemplates; - IDictionary> allParentContentTypeIds; + IDictionary> allAssociatedTemplates; + IDictionary> allParentContentTypeIds; var contentTypes = ContentTypeRepository.ContentTypeQueryMapper.MapContentTypes( - new[] {99997, 99998}, DatabaseContext.Database, SqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) + DatabaseContext.Database, SqlSyntax, out allAssociatedTemplates, out allParentContentTypeIds) + .Where(x => (new[] {99997, 99998}).Contains(x.Id)) .ToArray(); var contentType1 = contentTypes.SingleOrDefault(x => x.Id == 99997); @@ -109,9 +110,10 @@ namespace Umbraco.Tests.Persistence.Querying transaction.Complete(); } - IDictionary> allParentContentTypeIds; + IDictionary> allParentContentTypeIds; var contentTypes = ContentTypeRepository.ContentTypeQueryMapper.MapMediaTypes( - new[] { 99997, 99998 }, DatabaseContext.Database, SqlSyntax, out allParentContentTypeIds) + DatabaseContext.Database, SqlSyntax, out allParentContentTypeIds) + .Where(x => (new[] { 99997, 99998 }).Contains(x.Id)) .ToArray(); var contentType1 = contentTypes.SingleOrDefault(x => x.Id == 99997); From 9a830e5de61cf70fcbe77ecb3fa32c3f89fa1278 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 27 Jan 2016 19:44:36 +0100 Subject: [PATCH 069/406] fixes tests --- .../Cache/FullDataSetRepositoryCachePolicy.cs | 7 +++++-- .../Cache/FullDataSetRepositoryCachePolicyFactory.cs | 11 +++++++---- .../Persistence/Repositories/ContentTypeRepository.cs | 2 +- .../Persistence/Repositories/DomainRepository.cs | 2 +- .../Persistence/Repositories/LanguageRepository.cs | 2 +- .../Persistence/Repositories/MediaTypeRepository.cs | 2 +- .../Persistence/Repositories/MemberTypeRepository.cs | 2 +- .../Repositories/PublicAccessRepository.cs | 2 +- .../Persistence/Repositories/RepositoryBase.cs | 6 +++++- .../Persistence/Repositories/TemplateRepository.cs | 2 +- .../Cache/FullDataSetCachePolicyTests.cs | 8 ++++---- 11 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index b435615d54..c098af8992 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -18,7 +18,9 @@ namespace Umbraco.Core.Cache internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy where TEntity : class, IAggregateRoot { - public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache) : base(cache, + private readonly Func _getEntityId; + + public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func getEntityId) : base(cache, new RepositoryCachePolicyOptions { //Definitely allow zero'd cache entires since this is a full set, in many cases there will be none, @@ -26,6 +28,7 @@ namespace Umbraco.Core.Cache GetAllCacheAllowZeroCount = true }) { + _getEntityId = getEntityId; } private bool? _hasZeroCountCache; @@ -40,7 +43,7 @@ namespace Umbraco.Core.Cache // Now we can just filter by ids if they have been supplied return ids.Any() - ? result.Where(x => ids.Contains((TId) (object) x.Id)).ToArray() + ? result.Where(x => ids.Contains(_getEntityId(x))).ToArray() : result; } diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs index 75bdae7e83..6a79c2b8c2 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs @@ -1,3 +1,4 @@ +using System; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Cache @@ -11,15 +12,17 @@ namespace Umbraco.Core.Cache where TEntity : class, IAggregateRoot { private readonly IRuntimeCacheProvider _runtimeCache; - - public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache) + private readonly Func _getEntityId; + + public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, Func getEntityId) { - _runtimeCache = runtimeCache; + _runtimeCache = runtimeCache; + _getEntityId = getEntityId; } public virtual IRepositoryCachePolicy CreatePolicy() { - return new FullDataSetRepositoryCachePolicy(_runtimeCache); + return new FullDataSetRepositoryCachePolicy(_runtimeCache, _getEntityId); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 2fdc0e8613..1a09a2206d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -34,7 +34,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs index 6abab73dd7..563243f12c 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs @@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index 6fc9bd5ebc..3884eac888 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 9147cead55..4ee5e1a327 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index ca2b0715ac..4bfdbf3c8a 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index 1086b9cee0..1d8e56190b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -26,7 +26,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 88b8e93772..5534a9ea40 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -82,7 +82,11 @@ namespace Umbraco.Core.Persistence.Repositories { } - + + protected virtual TId GetEntityId(TEntity entity) + { + return (TId)(object)entity.Id; + } /// /// The runtime cache used for this repo by default is the isolated cache for this type diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index acc17b370a..3545bc1c55 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -51,7 +51,7 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); } } diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs index 3d3884c686..9187fe5b27 100644 --- a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -36,7 +36,7 @@ namespace Umbraco.Tests.Cache return cached.Any() ? new DeepCloneableList() : null; }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); using (defaultPolicy) { var found = defaultPolicy.GetAll(new object[] {}, o => new AuditItem[] {}); @@ -46,7 +46,7 @@ namespace Umbraco.Tests.Cache Assert.IsNotNull(list); //Do it again, ensure that its coming from the cache! - defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); using (defaultPolicy) { var found = defaultPolicy.GetAll(new object[] { }, o => new AuditItem[] { }); @@ -73,7 +73,7 @@ namespace Umbraco.Tests.Cache }); cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem[] { }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); using (defaultPolicy) { var found = defaultPolicy.GetAll(new object[] { }, o => new[] @@ -98,7 +98,7 @@ namespace Umbraco.Tests.Cache new AuditItem(2, "blah2", AuditType.Copy, 123) }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); using (defaultPolicy) { var found = defaultPolicy.GetAll(new object[] { }, o => new[] { (AuditItem)null }); From 93aadc196fae326818b12bd99584374ff719976a Mon Sep 17 00:00:00 2001 From: glennweb Date: Thu, 28 Jan 2016 15:07:18 +1000 Subject: [PATCH 070/406] Fixed incorrect case property in select statement causing case sensitive database's to throw error. https://our.umbraco.org/forum/core/general/49619-Went-wrong-in-a-moment-Invalid-column-name --- .../Persistence/Repositories/ContentTypeBaseRepository.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index eb8dfde6b0..a4ef11a645 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -866,7 +866,7 @@ AND umbracoNode.id <> @id", var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, @@ -1271,4 +1271,4 @@ AND umbracoNode.id <> @id", return PerformExists(id); } } -} \ No newline at end of file +} From 5d3e580cda4d78cbf0e5d2f3732fe37173e2ef1a Mon Sep 17 00:00:00 2001 From: glennweb Date: Thu, 28 Jan 2016 15:12:14 +1000 Subject: [PATCH 071/406] Saw there was another place with the incorrect case for the column name AllowedId --- .../Persistence/Repositories/ContentTypeBaseRepository.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index a4ef11a645..dd6f4114d6 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -732,7 +732,7 @@ AND umbracoNode.id <> @id", var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, From e9122385742bcc2865bad3b5ecb7689fda438d19 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 09:32:01 +0100 Subject: [PATCH 072/406] Saw there was another place with the incorrect case for the column name AllowedId --- .../Persistence/Repositories/ContentTypeBaseRepository.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 2f42919ce3..64617c92f2 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -710,7 +710,7 @@ AND umbracoNode.id <> @id", var sql = @"SELECT cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, ParentTypes.parentContentTypeId as chtParentId, ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, @@ -844,7 +844,7 @@ AND umbracoNode.id <> @id", var sql = @"SELECT cmsDocumentType.IsDefault as dtIsDefault, cmsDocumentType.templateNodeId as dtTemplateId, cmsContentType.pk as ctPk, cmsContentType.alias as ctAlias, cmsContentType.allowAtRoot as ctAllowAtRoot, cmsContentType.description as ctDesc, cmsContentType.icon as ctIcon, cmsContentType.isContainer as ctIsContainer, cmsContentType.nodeId as ctId, cmsContentType.thumbnail as ctThumb, - AllowedTypes.allowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, + AllowedTypes.AllowedId as ctaAllowedId, AllowedTypes.SortOrder as ctaSortOrder, AllowedTypes.alias as ctaAlias, ParentTypes.parentContentTypeId as chtParentId,ParentTypes.parentContentTypeKey as chtParentKey, umbracoNode.createDate as nCreateDate, umbracoNode." + sqlSyntax.GetQuotedColumnName("level") + @" as nLevel, umbracoNode.nodeObjectType as nObjectType, umbracoNode.nodeUser as nUser, umbracoNode.parentID as nParentId, umbracoNode." + sqlSyntax.GetQuotedColumnName("path") + @" as nPath, umbracoNode.sortOrder as nSortOrder, umbracoNode." + sqlSyntax.GetQuotedColumnName("text") + @" as nName, umbracoNode.trashed as nTrashed, @@ -1242,4 +1242,4 @@ AND umbracoNode.id <> @id", return PerformExists(id); } } -} \ No newline at end of file +} From 81a99d6f7dd893d09ea1556419279b97483a3bbd Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 10:19:51 +0100 Subject: [PATCH 073/406] adds null check and ensures that SetInternalRedirectPublishedContent cannot be called with a null result --- src/Umbraco.Web/Routing/PublishedContentRequest.cs | 3 ++- src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Web/Routing/PublishedContentRequest.cs b/src/Umbraco.Web/Routing/PublishedContentRequest.cs index 78f4449c88..02ffc475d3 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequest.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequest.cs @@ -166,7 +166,8 @@ namespace Umbraco.Web.Routing /// preserve or reset the template, if any. public void SetInternalRedirectPublishedContent(IPublishedContent content) { - EnsureWriteable(); + if (content == null) throw new ArgumentNullException("content"); + EnsureWriteable(); // unless a template has been set already by the finder, // template should be null at that point. diff --git a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs index 65371ecba4..2e075d8ed1 100644 --- a/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs +++ b/src/Umbraco.Web/Routing/PublishedContentRequestEngine.cs @@ -515,11 +515,11 @@ namespace Umbraco.Web.Routing { // redirect to another page var node = _routingContext.UmbracoContext.ContentCache.GetById(internalRedirectId); - - _pcr.SetInternalRedirectPublishedContent(node); // don't use .PublishedContent here + if (node != null) { - redirect = true; + _pcr.SetInternalRedirectPublishedContent(node); // don't use .PublishedContent here + redirect = true; ProfilingLogger.Logger.Debug("{0}Redirecting to id={1}", () => tracePrefix, () => internalRedirectId); } else From a0036d925ed6e2d80cf23898651f8461ac78a674 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 12:14:30 +0100 Subject: [PATCH 074/406] U4-7682 Add option to DatabaseServerMessengerOptions to force a Cold Boot if there are too many instructions --- .../Sync/DatabaseServerMessenger.cs | 24 +++++ .../Sync/DatabaseServerMessengerOptions.cs | 10 +- src/Umbraco.Web/WebBootManager.cs | 94 ++++++++++++------- 3 files changed, 93 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index a4b4d35d10..f74c45948f 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -157,6 +157,30 @@ namespace Umbraco.Core.Sync foreach (var callback in _options.InitializingCallbacks) callback(); } + else + { + //check for how many instructions there are to process + var count = _appContext.DatabaseContext.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId}); + if (count > _options.MaxProcessingInstructionCount) + { + //too many instructions, proceed to cold boot + _logger.Warn("The instruction count {0} exceeds the specified MaxProcessingInstructionCount {1}, proceeding to cold boot", + () => count, () => _options.MaxProcessingInstructionCount); + + // go get the last id in the db and store it + // note: do it BEFORE initializing otherwise some instructions might get lost + // when doing it before, some instructions might run twice - not an issue + var lastId = _appContext.DatabaseContext.Database.ExecuteScalar("SELECT MAX(id) FROM umbracoCacheInstruction"); + if (lastId > 0) + SaveLastSynced(lastId); + + // execute initializing callbacks + if (_options.InitializingCallbacks != null) + foreach (var callback in _options.InitializingCallbacks) + callback(); + + } + } _initialized = true; } diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs index f1bebce10b..37a463e2d7 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs @@ -2,7 +2,8 @@ using System; using System.Collections.Generic; namespace Umbraco.Core.Sync -{ +{ + /// /// Provides options to the . /// @@ -15,8 +16,15 @@ namespace Umbraco.Core.Sync { DaysToRetainInstructions = 2; // 2 days ThrottleSeconds = 5; // 5 seconds + + MaxProcessingInstructionCount = 1000; } + /// + /// If the number of instructions exceeds this amount during startup then the server will cold boot (rebuild it's own caches) + /// + public int MaxProcessingInstructionCount { get; set; } + /// /// A list of callbacks that will be invoked if the lastsynced.txt file does not exist. /// diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index a9495afa7c..497db6d17a 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -12,6 +12,7 @@ using System.Web.Routing; using ClientDependency.Core.Config; using Examine; using Examine.Config; +using Examine.Providers; using umbraco; using Umbraco.Core; using Umbraco.Core.Configuration; @@ -57,7 +58,7 @@ namespace Umbraco.Web { private readonly bool _isForTesting; //NOTE: see the Initialize method for what this is used for - private readonly List _indexesToRebuild = new List(); + private static readonly List IndexesToRebuild = new List(); public WebBootManager(UmbracoApplicationBase umbracoApplication) : base(umbracoApplication) @@ -210,9 +211,9 @@ namespace Umbraco.Web // (see the initialize method for notes) - we'll ensure we remove the event handler too in case examine manager doesn't actually // initialize during startup, in which case we want it to rebuild the indexes itself. ExamineManager.Instance.BuildingEmptyIndexOnStartup -= OnInstanceOnBuildingEmptyIndexOnStartup; - if (_indexesToRebuild.Any()) + if (IndexesToRebuild.Any()) { - foreach (var indexer in _indexesToRebuild) + foreach (var indexer in IndexesToRebuild) { indexer.RebuildIndex(); } @@ -403,9 +404,9 @@ namespace Umbraco.Web else { - // NOTE: This is IMPORTANT! ... we don't want to rebuild any index that is already flagged to be re-indexed - // on startup based on our _indexesToRebuild variable and how Examine auto-rebuilds when indexes are empty - // this callback is used below for the DatabaseServerMessenger startup options + //We are using a custom action here so we can check the examine settings value first, we don't want to + // put that check into the CreateIndexesOnColdBoot method because developers may choose to use this + // method directly and they will be in charge of this check if they need it Action rebuildIndexes = () => { //If the developer has explicitly opted out of rebuilding indexes on startup then we @@ -413,39 +414,30 @@ namespace Umbraco.Web // out of sync if they are auto-scaling but there's not much we can do about that. if (ExamineSettings.Instance.RebuildOnAppStart == false) return; - if (_indexesToRebuild.Any()) + foreach (var indexer in GetIndexesForColdBoot()) { - var otherIndexes = ExamineManager.Instance.IndexProviderCollection.Except(_indexesToRebuild); - foreach (var otherIndex in otherIndexes) - { - otherIndex.RebuildIndex(); - } - } - else - { - //rebuild them all - ExamineManager.Instance.RebuildIndex(); + indexer.RebuildIndex(); } }; ServerMessengerResolver.Current.SetServerMessenger(new BatchedDatabaseServerMessenger( - ApplicationContext, - true, - //Default options for web including the required callbacks to build caches - new DatabaseServerMessengerOptions - { - //These callbacks will be executed if the server has not been synced - // (i.e. it is a new server or the lastsynced.txt file has been removed) - InitializingCallbacks = new Action[] + ApplicationContext, + true, + //Default options for web including the required callbacks to build caches + new DatabaseServerMessengerOptions { - //rebuild the xml cache file if the server is not synced - () => global::umbraco.content.Instance.RefreshContentFromDatabase(), - //rebuild indexes if the server is not synced - // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific - // indexes then they can adjust this logic themselves. - rebuildIndexes - } - })); + //These callbacks will be executed if the server has not been synced + // (i.e. it is a new server or the lastsynced.txt file has been removed) + InitializingCallbacks = new Action[] + { + //rebuild the xml cache file if the server is not synced + () => global::umbraco.content.Instance.RefreshContentFromDatabase(), + //rebuild indexes if the server is not synced + // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific + // indexes then they can adjust this logic themselves. + rebuildIndexes + } + })); } SurfaceControllerResolver.Current = new SurfaceControllerResolver( @@ -533,12 +525,46 @@ namespace Umbraco.Web new DefaultCultureDictionaryFactory()); } + /// + /// The method used to create indexes on a cold boot + /// + /// + /// A cold boot is when the server determines it will not (or cannot) process instructions in the cache table and + /// will rebuild it's own caches itself. + /// + public static IEnumerable GetIndexesForColdBoot() + { + // NOTE: This is IMPORTANT! ... we don't want to rebuild any index that is already flagged to be re-indexed + // on startup based on our _indexesToRebuild variable and how Examine auto-rebuilds when indexes are empty + // this callback is used below for the DatabaseServerMessenger startup options + + if (IndexesToRebuild.Any()) + { + var otherIndexes = ExamineManager.Instance.IndexProviderCollection.Cast().Except(IndexesToRebuild); + + foreach (var otherIndex in otherIndexes) + { + yield return otherIndex; + } + } + else + { + foreach (var index in ExamineManager.Instance.IndexProviderCollection.Cast()) + { + yield return index; + } + } + + IndexesToRebuild.Clear(); + } + + private void OnInstanceOnBuildingEmptyIndexOnStartup(object sender, BuildingEmptyIndexOnStartupEventArgs args) { //store the indexer that needs rebuilding because it's empty for when the boot process // is complete and cancel this current event so the rebuild process doesn't start right now. args.Cancel = true; - _indexesToRebuild.Add(args.Indexer); + IndexesToRebuild.Add((BaseIndexProvider)args.Indexer); } } } From ec815837415e4012af6542fd96c19f7a51dfb608 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 13:50:45 +0100 Subject: [PATCH 075/406] Fixes case sensitive matching on alias in TemplateRepository --- .../Repositories/TemplateRepository.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index acc17b370a..0433340599 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -488,7 +488,7 @@ namespace Umbraco.Core.Persistence.Repositories var parent = all.FirstOrDefault(x => x.Id == masterTemplateId); if (parent == null) return Enumerable.Empty(); - var children = all.Where(x => x.MasterTemplateAlias == parent.Alias); + var children = all.Where(x => x.MasterTemplateAlias.InvariantEquals(parent.Alias)); return children; } @@ -497,7 +497,7 @@ namespace Umbraco.Core.Persistence.Repositories //return from base.GetAll, this is all cached return base.GetAll().Where(x => alias.IsNullOrWhiteSpace() ? x.MasterTemplateAlias.IsNullOrWhiteSpace() - : x.MasterTemplateAlias == alias); + : x.MasterTemplateAlias.InvariantEquals(alias)); } public IEnumerable GetDescendants(int masterTemplateId) @@ -532,7 +532,7 @@ namespace Umbraco.Core.Persistence.Repositories var descendants = new List(); if (alias.IsNullOrWhiteSpace() == false) { - var parent = all.FirstOrDefault(x => x.Alias == alias); + var parent = all.FirstOrDefault(x => x.Alias.InvariantEquals(alias)); if (parent == null) return Enumerable.Empty(); //recursively add all children AddChildren(all, descendants, parent.Alias); @@ -552,7 +552,7 @@ namespace Umbraco.Core.Persistence.Repositories private void AddChildren(ITemplate[] all, List descendants, string masterAlias) { - var c = all.Where(x => x.MasterTemplateAlias == masterAlias).ToArray(); + var c = all.Where(x => x.MasterTemplateAlias.InvariantEquals(masterAlias)).ToArray(); descendants.AddRange(c); if (c.Any() == false) return; //recurse through all children @@ -573,7 +573,7 @@ namespace Umbraco.Core.Persistence.Repositories //first get all template objects var allTemplates = base.GetAll().ToArray(); - var selfTemplate = allTemplates.SingleOrDefault(x => x.Alias == alias); + var selfTemplate = allTemplates.SingleOrDefault(x => x.Alias.InvariantEquals(alias)); if (selfTemplate == null) { return null; @@ -582,11 +582,11 @@ namespace Umbraco.Core.Persistence.Repositories var top = selfTemplate; while (top.MasterTemplateAlias.IsNullOrWhiteSpace() == false) { - top = allTemplates.Single(x => x.Alias == top.MasterTemplateAlias); + top = allTemplates.Single(x => x.Alias.InvariantEquals(top.MasterTemplateAlias)); } var topNode = new TemplateNode(allTemplates.Single(x => x.Id == top.Id)); - var childTemplates = allTemplates.Where(x => x.MasterTemplateAlias == top.Alias); + var childTemplates = allTemplates.Where(x => x.MasterTemplateAlias.InvariantEquals(top.Alias)); //This now creates the hierarchy recursively topNode.Children = CreateChildren(topNode, childTemplates, allTemplates); @@ -598,7 +598,7 @@ namespace Umbraco.Core.Persistence.Repositories private static TemplateNode WalkTree(TemplateNode current, string alias) { //now walk the tree to find the node - if (current.Template.Alias == alias) + if (current.Template.Alias.InvariantEquals(alias)) { return current; } @@ -730,7 +730,7 @@ namespace Umbraco.Core.Persistence.Repositories //get this node's children var local = childTemplate; - var kids = allTemplates.Where(x => x.MasterTemplateAlias == local.Alias); + var kids = allTemplates.Where(x => x.MasterTemplateAlias.InvariantEquals(local.Alias)); //recurse child.Children = CreateChildren(child, kids, allTemplates); @@ -760,7 +760,7 @@ namespace Umbraco.Core.Persistence.Repositories private bool AliasAlreadExists(ITemplate template) { - var sql = GetBaseQuery(true).Where(x => x.Alias == template.Alias && x.NodeId != template.Id); + var sql = GetBaseQuery(true).Where(x => x.Alias.InvariantEquals(template.Alias) && x.NodeId != template.Id); var count = Database.ExecuteScalar(sql); return count > 0; } From 1fcea083e76b875d4b3a3592cddd455b8ef64bc9 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 14:10:13 +0100 Subject: [PATCH 076/406] Ensures no profiling occurs when not in debug mode --- src/Umbraco.Core/Profiling/WebProfiler.cs | 37 ++++++++++++++--------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/src/Umbraco.Core/Profiling/WebProfiler.cs b/src/Umbraco.Core/Profiling/WebProfiler.cs index 7e2cf49313..45d3a68591 100644 --- a/src/Umbraco.Core/Profiling/WebProfiler.cs +++ b/src/Umbraco.Core/Profiling/WebProfiler.cs @@ -19,18 +19,21 @@ namespace Umbraco.Core.Profiling /// internal WebProfiler() { - //setup some defaults - MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); - MiniProfiler.Settings.StackMaxLength = 5000; + if (GlobalSettings.DebugMode) + { + //setup some defaults + MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); + MiniProfiler.Settings.StackMaxLength = 5000; - //At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext - // since it hasn't started yet. So we need to do some hacking to enable profiling during startup. - _startupWebProfilerProvider = new StartupWebProfilerProvider(); - //this should always be the case during startup, we'll need to set a custom profiler provider - MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider; + //At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext + // since it hasn't started yet. So we need to do some hacking to enable profiling during startup. + _startupWebProfilerProvider = new StartupWebProfilerProvider(); + //this should always be the case during startup, we'll need to set a custom profiler provider + MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider; - //Binds to application events to enable the MiniProfiler with a real HttpRequest - UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; + //Binds to application events to enable the MiniProfiler with a real HttpRequest + UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; + } } /// @@ -123,7 +126,7 @@ namespace Umbraco.Core.Profiling /// public string Render() { - return MiniProfiler.RenderIncludes(RenderPosition.Right).ToString(); + return GlobalSettings.DebugMode ? MiniProfiler.RenderIncludes(RenderPosition.Right).ToString() : string.Empty; } /// @@ -143,8 +146,11 @@ namespace Umbraco.Core.Profiling /// Start the profiler /// public void Start() - { - MiniProfiler.Start(); + { + if (GlobalSettings.DebugMode) + { + MiniProfiler.Start(); + } } /// @@ -156,7 +162,10 @@ namespace Umbraco.Core.Profiling /// public void Stop(bool discardResults = false) { - MiniProfiler.Stop(discardResults); + if (GlobalSettings.DebugMode) + { + MiniProfiler.Stop(discardResults); + } } /// From c9c451be65ec552edc55921871d8f03ea2bd87b6 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 28 Jan 2016 14:19:32 +0100 Subject: [PATCH 077/406] U4-7682 Minor review adjustments --- .../Sync/DatabaseServerMessenger.cs | 72 ++++++++-------- .../Sync/DatabaseServerMessengerOptions.cs | 6 +- src/Umbraco.Web/WebBootManager.cs | 82 +++++++++---------- 3 files changed, 76 insertions(+), 84 deletions(-) diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index f74c45948f..8a4d79725a 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -60,7 +60,7 @@ namespace Umbraco.Core.Sync protected override bool RequiresDistributed(IEnumerable servers, ICacheRefresher refresher, MessageType dispatchType) { - // we don't care if there's servers listed or not, + // we don't care if there's servers listed or not, // if distributed call is enabled we will make the call return _initialized && DistributedEnabled; } @@ -139,12 +139,35 @@ namespace Umbraco.Core.Sync { if (_released) return; + var coldboot = false; if (_lastId < 0) // never synced before { - // we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new + // we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new // server and it will need to rebuild it's own caches, eg Lucene or the xml cache file. - _logger.Warn("No last synced Id found, this generally means this is a new server/install. The server will rebuild its caches and indexes and then adjust it's last synced id to the latest found in the database and will start maintaining cache updates based on that id"); + _logger.Warn("No last synced Id found, this generally means this is a new server/install." + + " The server will build its caches and indexes, and then adjust its last synced Id to the latest found in" + + " the database and maintain cache updates based on that Id."); + coldboot = true; + } + else + { + //check for how many instructions there are to process + var count = _appContext.DatabaseContext.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId}); + if (count > _options.MaxProcessingInstructionCount) + { + //too many instructions, proceed to cold boot + _logger.Warn("The instruction count ({0}) exceeds the specified MaxProcessingInstructionCount ({1})." + + " The server will skip existing instructions, rebuild its caches and indexes entirely, adjust its last synced Id" + + " to the latest found in the database and maintain cache updates based on that Id.", + () => count, () => _options.MaxProcessingInstructionCount); + + coldboot = true; + } + } + + if (coldboot) + { // go get the last id in the db and store it // note: do it BEFORE initializing otherwise some instructions might get lost // when doing it before, some instructions might run twice - not an issue @@ -157,30 +180,6 @@ namespace Umbraco.Core.Sync foreach (var callback in _options.InitializingCallbacks) callback(); } - else - { - //check for how many instructions there are to process - var count = _appContext.DatabaseContext.Database.ExecuteScalar("SELECT COUNT(*) FROM umbracoCacheInstruction WHERE id > @lastId", new {lastId = _lastId}); - if (count > _options.MaxProcessingInstructionCount) - { - //too many instructions, proceed to cold boot - _logger.Warn("The instruction count {0} exceeds the specified MaxProcessingInstructionCount {1}, proceeding to cold boot", - () => count, () => _options.MaxProcessingInstructionCount); - - // go get the last id in the db and store it - // note: do it BEFORE initializing otherwise some instructions might get lost - // when doing it before, some instructions might run twice - not an issue - var lastId = _appContext.DatabaseContext.Database.ExecuteScalar("SELECT MAX(id) FROM umbracoCacheInstruction"); - if (lastId > 0) - SaveLastSynced(lastId); - - // execute initializing callbacks - if (_options.InitializingCallbacks != null) - foreach (var callback in _options.InitializingCallbacks) - callback(); - - } - } _initialized = true; } @@ -193,7 +192,7 @@ namespace Umbraco.Core.Sync { lock (_locko) { - if (_syncing) + if (_syncing) return; if (_released) @@ -237,9 +236,9 @@ namespace Umbraco.Core.Sync private void ProcessDatabaseInstructions() { // NOTE - // we 'could' recurse to ensure that no remaining instructions are pending in the table before proceeding but I don't think that + // we 'could' recurse to ensure that no remaining instructions are pending in the table before proceeding but I don't think that // would be a good idea since instructions could keep getting added and then all other threads will probably get stuck from serving requests - // (depending on what the cache refreshers are doing). I think it's best we do the one time check, process them and continue, if there are + // (depending on what the cache refreshers are doing). I think it's best we do the one time check, process them and continue, if there are // pending requests after being processed, they'll just be processed on the next poll. // // FIXME not true if we're running on a background thread, assuming we can? @@ -305,7 +304,7 @@ namespace Umbraco.Core.Sync /// Remove old instructions from the database /// /// - /// Always leave the last (most recent) record in the db table, this is so that not all instructions are removed which would cause + /// Always leave the last (most recent) record in the db table, this is so that not all instructions are removed which would cause /// the site to cold boot if there's been no instruction activity for more than DaysToRetainInstructions. /// See: http://issues.umbraco.org/issue/U4-7643#comment=67-25085 /// @@ -314,15 +313,15 @@ namespace Umbraco.Core.Sync var pruneDate = DateTime.UtcNow.AddDays(-_options.DaysToRetainInstructions); var sqlSyntax = _appContext.DatabaseContext.SqlSyntax; - //NOTE: this query could work on SQL server and MySQL: + //NOTE: this query could work on SQL server and MySQL: /* SELECT id FROM umbracoCacheInstruction - WHERE utcStamp < getdate() + WHERE utcStamp < getdate() AND id <> (SELECT MAX(id) FROM umbracoCacheInstruction) */ // However, this will not work on SQLCE and in fact it will be slower than the query we are - // using if the SQL server doesn't perform it's own query optimizations (i.e. since the above + // using if the SQL server doesn't perform it's own query optimizations (i.e. since the above // query could actually execute a sub query for every row found). So we've had to go with an // inner join which is faster and works on SQLCE but it's uglier to read. @@ -355,9 +354,9 @@ namespace Umbraco.Core.Sync var dtos = _appContext.DatabaseContext.Database.Fetch(sql); if (dtos.Count == 0) - _lastId = -1; + _lastId = -1; } - + /// /// Reads the last-synced id from file into memory. /// @@ -526,4 +525,3 @@ namespace Umbraco.Core.Sync #endregion } } - \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs index 37a463e2d7..7559c37813 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessengerOptions.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; namespace Umbraco.Core.Sync { - /// /// Provides options to the . /// @@ -15,13 +14,12 @@ namespace Umbraco.Core.Sync public DatabaseServerMessengerOptions() { DaysToRetainInstructions = 2; // 2 days - ThrottleSeconds = 5; // 5 seconds - + ThrottleSeconds = 5; // 5 second MaxProcessingInstructionCount = 1000; } /// - /// If the number of instructions exceeds this amount during startup then the server will cold boot (rebuild it's own caches) + /// The maximum number of instructions that can be processed at startup; otherwise the server cold-boots (rebuilds its caches). /// public int MaxProcessingInstructionCount { get; set; } diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 497db6d17a..3ca8903c98 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -52,13 +52,13 @@ using ProfilingViewEngine = Umbraco.Core.Profiling.ProfilingViewEngine; namespace Umbraco.Web { /// - /// A bootstrapper for the Umbraco application which initializes all objects including the Web portion of the application + /// A bootstrapper for the Umbraco application which initializes all objects including the Web portion of the application /// public class WebBootManager : CoreBootManager { private readonly bool _isForTesting; //NOTE: see the Initialize method for what this is used for - private static readonly List IndexesToRebuild = new List(); + private static readonly List IndexesToRebuild = new List(); public WebBootManager(UmbracoApplicationBase umbracoApplication) : base(umbracoApplication) @@ -105,7 +105,7 @@ namespace Umbraco.Web public override IBootManager Initialize() { //This is basically a hack for this item: http://issues.umbraco.org/issue/U4-5976 - // when Examine initializes it will try to rebuild if the indexes are empty, however in many cases not all of Examine's + // when Examine initializes it will try to rebuild if the indexes are empty, however in many cases not all of Examine's // event handlers will be assigned during bootup when the rebuilding starts which is a problem. So with the examine 0.1.58.2941 build // it has an event we can subscribe to in order to cancel this rebuilding process, but what we'll do is cancel it and postpone the rebuilding until the // boot process has completed. It's a hack but it works. @@ -146,7 +146,7 @@ namespace Umbraco.Web { "compositeFileHandlerPath", ClientDependencySettings.Instance.CompositeFileHandlerPath } }); ClientDependencySettings.Instance.MvcRendererCollection.Add(renderer); - + // Disable the X-AspNetMvc-Version HTTP Header MvcHandler.DisableMvcResponseHeader = true; @@ -155,9 +155,9 @@ namespace Umbraco.Web return this; } - + /// - /// Override this method in order to ensure that the UmbracoContext is also created, this can only be + /// Override this method in order to ensure that the UmbracoContext is also created, this can only be /// created after resolution is frozen! /// protected override void FreezeResolution() @@ -171,8 +171,8 @@ namespace Umbraco.Web httpContext, ApplicationContext, new WebSecurity(httpContext, ApplicationContext), - UmbracoConfig.For.UmbracoSettings(), - UrlProviderResolver.Current.Providers, + UmbracoConfig.For.UmbracoSettings(), + UrlProviderResolver.Current.Providers, false); } @@ -188,7 +188,7 @@ namespace Umbraco.Web ProfilerResolver.Current.SetProfiler(profiler); profiler.Start(); } - + /// /// Ensure that the OnApplicationStarted methods of the IApplicationEvents are called /// @@ -206,8 +206,8 @@ namespace Umbraco.Web //Now, startup all of our legacy startup handler ApplicationEventsResolver.Current.InstantiateLegacyStartupHandlers(); - - //Ok, now that everything is complete we'll check if we've stored any references to index that need rebuilding and run them + + //Ok, now that everything is complete we'll check if we've stored any references to index that need rebuilding and run them // (see the initialize method for notes) - we'll ensure we remove the event handler too in case examine manager doesn't actually // initialize during startup, in which case we want it to rebuild the indexes itself. ExamineManager.Instance.BuildingEmptyIndexOnStartup -= OnInstanceOnBuildingEmptyIndexOnStartup; @@ -245,14 +245,14 @@ namespace Umbraco.Web { //create a web-based cache helper var cacheHelper = new CacheHelper( - //we need to have the dep clone runtime cache provider to ensure + //we need to have the dep clone runtime cache provider to ensure //all entities are cached properly (cloned in and cloned out) new DeepCloneRuntimeCacheProvider(new HttpRuntimeCacheProvider(HttpRuntime.Cache)), new StaticCacheProvider(), //we have no request based cache when not running in web-based context new NullCacheProvider(), new IsolatedRuntimeCache(type => - //we need to have the dep clone runtime cache provider to ensure + //we need to have the dep clone runtime cache provider to ensure //all entities are cached properly (cloned in and cloned out) new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); @@ -283,7 +283,7 @@ namespace Umbraco.Web //plugin controllers must come first because the next route will catch many things RoutePluginControllers(); } - + private void RoutePluginControllers() { var umbracoPath = GlobalSettings.UmbracoMvcArea; @@ -350,14 +350,14 @@ namespace Umbraco.Web umbracoPath + "/Surface/" + meta.ControllerName + "/{action}/{id}",//url to match new { controller = meta.ControllerName, action = "Index", id = UrlParameter.Optional }, new[] { meta.ControllerNamespace }); //look in this namespace to create the controller - route.DataTokens.Add("umbraco", "surface"); //ensure the umbraco token is set + route.DataTokens.Add("umbraco", "surface"); //ensure the umbraco token is set route.DataTokens.Add("UseNamespaceFallback", false); //Don't look anywhere else except this namespace! //make it use our custom/special SurfaceMvcHandler route.RouteHandler = new SurfaceRouteHandler(); } /// - /// Initializes all web based and core resolves + /// Initializes all web based and core resolves /// protected override void InitializeResolvers() { @@ -376,7 +376,7 @@ namespace Umbraco.Web //set the legacy one by default - this maintains backwards compat ServerMessengerResolver.Current.SetServerMessenger(new BatchedWebServiceServerMessenger(() => { - //we should not proceed to change this if the app/database is not configured since there will + //we should not proceed to change this if the app/database is not configured since there will // be no user, plus we don't need to have server messages sent if this is the case. if (ApplicationContext.IsConfigured && ApplicationContext.DatabaseContext.IsDatabaseConfigured) { @@ -404,12 +404,12 @@ namespace Umbraco.Web else { - //We are using a custom action here so we can check the examine settings value first, we don't want to - // put that check into the CreateIndexesOnColdBoot method because developers may choose to use this + //We are using a custom action here so we can check the examine settings value first, we don't want to + // put that check into the CreateIndexesOnColdBoot method because developers may choose to use this // method directly and they will be in charge of this check if they need it Action rebuildIndexes = () => { - //If the developer has explicitly opted out of rebuilding indexes on startup then we + //If the developer has explicitly opted out of rebuilding indexes on startup then we // should adhere to that and not do it, this means that if they are load balancing things will be // out of sync if they are auto-scaling but there's not much we can do about that. if (ExamineSettings.Instance.RebuildOnAppStart == false) return; @@ -433,8 +433,8 @@ namespace Umbraco.Web //rebuild the xml cache file if the server is not synced () => global::umbraco.content.Instance.RefreshContentFromDatabase(), //rebuild indexes if the server is not synced - // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific - // indexes then they can adjust this logic themselves. + // NOTE: This will rebuild ALL indexes including the members, if developers want to target specific + // indexes then they can adjust this logic themselves. rebuildIndexes } })); @@ -462,7 +462,7 @@ namespace Umbraco.Web new PublishedCache.XmlPublishedCache.PublishedContentCache(), new PublishedCache.XmlPublishedCache.PublishedMediaCache(ApplicationContext))); - GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), + GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerSelector), new NamespaceHttpControllerSelector(GlobalConfiguration.Configuration)); FilteredControllerFactoriesResolver.Current = new FilteredControllerFactoriesResolver( @@ -527,41 +527,37 @@ namespace Umbraco.Web /// /// The method used to create indexes on a cold boot - /// + /// /// /// A cold boot is when the server determines it will not (or cannot) process instructions in the cache table and /// will rebuild it's own caches itself. /// public static IEnumerable GetIndexesForColdBoot() { - // NOTE: This is IMPORTANT! ... we don't want to rebuild any index that is already flagged to be re-indexed - // on startup based on our _indexesToRebuild variable and how Examine auto-rebuilds when indexes are empty - // this callback is used below for the DatabaseServerMessenger startup options - + // NOTE: This is IMPORTANT! ... we don't want to rebuild any index that is already flagged to be re-indexed + // on startup based on our _indexesToRebuild variable and how Examine auto-rebuilds when indexes are empty. + // This callback is used above for the DatabaseServerMessenger startup options. + + // all indexes + IEnumerable indexes = ExamineManager.Instance.IndexProviderCollection; + + // except those that are already flagged + // and are processed in Complete() if (IndexesToRebuild.Any()) - { - var otherIndexes = ExamineManager.Instance.IndexProviderCollection.Cast().Except(IndexesToRebuild); + indexes = indexes.Except(IndexesToRebuild); - foreach (var otherIndex in otherIndexes) - { - yield return otherIndex; - } - } - else - { - foreach (var index in ExamineManager.Instance.IndexProviderCollection.Cast()) - { - yield return index; - } - } + // return + foreach (var index in indexes) + yield return index; + // and clear IndexesToRebuild.Clear(); } private void OnInstanceOnBuildingEmptyIndexOnStartup(object sender, BuildingEmptyIndexOnStartupEventArgs args) { - //store the indexer that needs rebuilding because it's empty for when the boot process + //store the indexer that needs rebuilding because it's empty for when the boot process // is complete and cancel this current event so the rebuild process doesn't start right now. args.Cancel = true; IndexesToRebuild.Add((BaseIndexProvider)args.Indexer); From 256849f8a65a48c178dcbb7cfcd78289a61f2432 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 14:19:38 +0100 Subject: [PATCH 078/406] adds nicer profiling during startup for app event handlers. --- src/Umbraco.Core/CoreBootManager.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 17f909da8a..f8dd255095 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -136,7 +136,10 @@ namespace Umbraco.Core { try { - x.OnApplicationInitialized(UmbracoApplication, ApplicationContext); + using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationInitialized", x.GetType()))) + { + x.OnApplicationInitialized(UmbracoApplication, ApplicationContext); + } } catch (Exception ex) { @@ -299,7 +302,10 @@ namespace Umbraco.Core { try { - x.OnApplicationStarting(UmbracoApplication, ApplicationContext); + using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationStarting", x.GetType()))) + { + x.OnApplicationStarting(UmbracoApplication, ApplicationContext); + } } catch (Exception ex) { @@ -350,7 +356,10 @@ namespace Umbraco.Core { try { - x.OnApplicationStarted(UmbracoApplication, ApplicationContext); + using (ProfilingLogger.DebugDuration(string.Format("Executing {0} in ApplicationStarted", x.GetType()))) + { + x.OnApplicationStarted(UmbracoApplication, ApplicationContext); + } } catch (Exception ex) { From e0ba030b1f7085603fb1658d4c26072c4d18f635 Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Thu, 28 Jan 2016 14:19:46 +0100 Subject: [PATCH 079/406] fixes U4-7851 Grid layout RTE formats not being applied. --- .../src/common/directives/components/grid/grid.rte.directive.js | 2 +- .../src/views/propertyeditors/rte/rte.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index 0fccd4eabb..26c646a569 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -30,7 +30,7 @@ angular.module("umbraco.directives") //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce - var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align]"; + var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align],span[id|class|style]"; var invalidElements = tinyMceConfig.inValidElements; var plugins = _.map(tinyMceConfig.plugins, function (plugin) { diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index 928c504937..f6cacfaf1f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -34,7 +34,7 @@ angular.module("umbraco") //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce - var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style]"; + var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style], span[id|class|style]"; var invalidElements = tinyMceConfig.inValidElements; var plugins = _.map(tinyMceConfig.plugins, function (plugin) { From 549a8380c363de0e8fff00e60c6196a32a289b0f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Thu, 28 Jan 2016 14:23:53 +0100 Subject: [PATCH 080/406] Fixes: U4-7806 Possible to interactive with property editor "preview" in document type editor --- .../components/property/umbpropertyeditor.directive.js | 5 +++-- src/Umbraco.Web.UI.Client/src/less/belle.less | 1 + .../src/less/components/umb-group-builder.less | 5 ++--- .../src/less/components/umb-property-editor.less | 3 +++ .../src/views/components/property/umb-property-editor.html | 3 ++- .../src/views/components/umb-groups-builder.html | 7 ++++--- 6 files changed, 15 insertions(+), 9 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/less/components/umb-property-editor.less diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js index 9a032d569f..5bca761162 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbpropertyeditor.directive.js @@ -11,7 +11,8 @@ var _umbPropertyEditor = function (umbPropEditorHelper) { return { scope: { model: "=", - isPreValue: "@" + isPreValue: "@", + preview: "@" }, require: "^form", @@ -38,4 +39,4 @@ var _umbPropertyEditor = function (umbPropEditorHelper) { //Preffered is the umb-property-editor as its more explicit - but we keep umb-editor for backwards compat angular.module("umbraco.directives").directive('umbPropertyEditor', _umbPropertyEditor); -angular.module("umbraco.directives").directive('umbEditor', _umbPropertyEditor); \ No newline at end of file +angular.module("umbraco.directives").directive('umbEditor', _umbPropertyEditor); diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 1400547383..a7335d8314 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -106,6 +106,7 @@ @import "components/overlays/umb-overlay-backdrop.less"; @import "components/umb-grid.less"; @import "components/umb-empty-state.less"; +@import "components/umb-property-editor.less"; @import "components/buttons/umb-button.less"; @import "components/buttons/umb-button-group.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less index d61f5dece2..56698c6c9c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-group-builder.less @@ -293,14 +293,13 @@ input.umb-group-builder__group-sort-value { display:none !important; } -.umb-group-builder__property-preview-overlay { +.umb-group-builder__property-preview-background { position: absolute; top: 0; left: 0; width: 100%; height: 100%; - z-index: 10; - border-radius: 10px; + z-index: -1; background: url("../img/checkered-background.png"); opacity: 0.2; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-editor.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-editor.less new file mode 100644 index 0000000000..cbea6987e7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-editor.less @@ -0,0 +1,3 @@ +.umb-property-editor.-not-clickable { + pointer-events: none; +} diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html index 80e854e9c2..bbf70f7a8e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html @@ -1,2 +1,3 @@ - +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html index 06ed26a155..fd169ca86a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-groups-builder.html @@ -179,8 +179,6 @@
    - -
    @@ -223,10 +221,13 @@ + model="property" + preview="true"> +
    +
    From ec0a9493e2f595addae20f0e47ef2350b508d67a Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Thu, 28 Jan 2016 14:25:40 +0100 Subject: [PATCH 081/406] removed whitespace issue --- .../propertyeditors/rte/rte.controller.js | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index f6cacfaf1f..89c3e2b264 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -34,7 +34,7 @@ angular.module("umbraco") //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce - var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style], span[id|class|style]"; + var extendedValidElements = "@[id|class|style],-div[id|dir|class|align|style],ins[datetime|cite],-ul[class|style],-li[class|style],span[id|class|style]"; var invalidElements = tinyMceConfig.inValidElements; var plugins = _.map(tinyMceConfig.plugins, function (plugin) { @@ -125,7 +125,7 @@ angular.module("umbraco") if (tinyMceConfig.customConfig) { - //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to + //if there is some custom config, we need to see if the string value of each item might actually be json and if so, we need to // convert it to json instead of having it as a string since this is what tinymce requires for (var i in tinyMceConfig.customConfig) { var val = tinyMceConfig.customConfig[i]; @@ -134,7 +134,7 @@ angular.module("umbraco") if (val.detectIsJson()) { try { tinyMceConfig.customConfig[i] = JSON.parse(val); - //now we need to check if this custom config key is defined in our baseline, if it is we don't want to + //now we need to check if this custom config key is defined in our baseline, if it is we don't want to //overwrite the baseline config item if it is an array, we want to concat the items in the array, otherwise //if it's an object it will overwrite the baseline if (angular.isArray(baseLineConfigObj[i]) && angular.isArray(tinyMceConfig.customConfig[i])) { @@ -144,7 +144,7 @@ angular.module("umbraco") } catch (e) { //cannot parse, we'll just leave it - } + } } } } @@ -164,13 +164,13 @@ angular.module("umbraco") editor.getBody().setAttribute('spellcheck', true); }); - //We need to listen on multiple things here because of the nature of tinymce, it doesn't + //We need to listen on multiple things here because of the nature of tinymce, it doesn't //fire events when you think! //The change event doesn't fire when content changes, only when cursor points are changed and undo points - //are created. the blur event doesn't fire if you insert content into the editor with a button and then - //press save. - //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can - //listen to both change and blur and also on our own 'saving' event. I think this will be best because a + //are created. the blur event doesn't fire if you insert content into the editor with a button and then + //press save. + //We have a couple of options, one is to do a set timeout and check for isDirty on the editor, or we can + //listen to both change and blur and also on our own 'saving' event. I think this will be best because a //timer might end up using unwanted cpu and we'd still have to listen to our saving event in case they clicked //save before the timeout elapsed. @@ -190,7 +190,7 @@ angular.module("umbraco") // currForm.$setDirty(); // alreadyDirty = true; // } - + // }); //}); @@ -225,7 +225,7 @@ angular.module("umbraco") var srcAttr = $(e.target).attr("src"); var path = srcAttr.split("?")[0]; $(e.target).attr("data-mce-src", path + qs); - + syncContent(editor); }); @@ -336,7 +336,7 @@ angular.module("umbraco") }); //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom + // NOTE: this is very important otherwise if this is part of a modal, the listener still exists because the dom // element might still be there even after the modal has been hidden. $scope.$on('$destroy', function () { unsubscribe(); From a6093f41bd2b5b490dd497f56203200ee374f181 Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 18:02:22 +0100 Subject: [PATCH 082/406] Fixes installer login for user and adds notes --- src/Umbraco.Core/Services/ContentService.cs | 7 ++++++ .../Services/ContentTypeService.cs | 7 ++++++ .../InstallSteps/SetUmbracoVersionStep.cs | 25 +++++++++++-------- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 2edc32f367..7c34709787 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1227,6 +1227,13 @@ namespace Umbraco.Core.Services /// Optional Id of the user issueing the delete operation public void DeleteContentOfType(int contentTypeId, int userId = 0) { + //TODO: This currently this is called from the ContentTypeService but that needs to change, + // if we are deleting a content type, we should just delete the data and do this operation slightly differently. + // This method will recursively go lookup every content item, check if any of it's descendants are + // of a different type, move them to the recycle bin, then permanently delete the content items. + // The main problem with this is that for every content item being deleted, events are raised... + // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. + using (new WriteLock(Locker)) { using (var uow = UowProvider.GetUnitOfWork()) diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index e6ead68810..1401659bf3 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -452,6 +452,13 @@ namespace Umbraco.Core.Services using (new WriteLock(Locker)) { + + //TODO: This needs to change, if we are deleting a content type, we should just delete the data, + // this method will recursively go lookup every content item, check if any of it's descendants are + // of a different type, move them to the recycle bin, then permanently delete the content items. + // The main problem with this is that for every content item being deleted, events are raised... + // which we need for many things like keeping caches in sync, but we can surely do this MUCH better. + _contentService.DeleteContentOfType(contentType.Id); var uow = UowProvider.GetUnitOfWork(); diff --git a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs index f7c9b3d6a5..78fe6d3766 100644 --- a/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/SetUmbracoVersionStep.cs @@ -27,6 +27,20 @@ namespace Umbraco.Web.Install.InstallSteps public override InstallSetupResult Execute(object model) { + var ih = new InstallHelper(UmbracoContext.Current); + + //During a new install we'll log the default user in (which is id = 0). + // During an upgrade, the user will already need to be logged in in order to run the installer. + + var security = new WebSecurity(_httpContext, _applicationContext); + //we do this check here because for upgrades the user will already be logged in, for brand new installs, + // they will not be logged in, however we cannot check the current installation status because it will tell + // us that it is in 'upgrade' because we already have a database conn configured and a database. + if (security.IsAuthenticated() == false && GlobalSettings.ConfigurationStatus.IsNullOrWhiteSpace()) + { + security.PerformLogin(0); + } + //This is synonymous with library.RefreshContent() - but we don't want to use library // for anything anymore so welll use the method that it is wrapping. This will just make sure // the correct xml structure exists in the xml cache file. This is required by some upgrade scripts @@ -39,17 +53,8 @@ namespace Umbraco.Web.Install.InstallSteps // Update ClientDependency version var clientDependencyConfig = new ClientDependencyConfiguration(_applicationContext.ProfilingLogger.Logger); var clientDependencyUpdated = clientDependencyConfig.IncreaseVersionNumber(); - - //During a new install we'll log the default user in (which is id = 0). - // During an upgrade, the user will already need to be logged in in order to run the installer. - if (InstallTypeTarget == InstallationType.NewInstall) - { - var security = new WebSecurity(_httpContext, _applicationContext); - security.PerformLogin(0); - } - //reports the ended install - var ih = new InstallHelper(UmbracoContext.Current); + //reports the ended install ih.InstallStatus(true, ""); return null; From 6faa7e2fc91edc14a0136a7357652610ea8e52df Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 18:31:47 +0100 Subject: [PATCH 083/406] dont' have RebuildOnAppStart set for dev purposes! --- src/Umbraco.Web.UI/config/ExamineSettings.config | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Umbraco.Web.UI/config/ExamineSettings.config b/src/Umbraco.Web.UI/config/ExamineSettings.config index 6759b5a21d..4e82ca2bb8 100644 --- a/src/Umbraco.Web.UI/config/ExamineSettings.config +++ b/src/Umbraco.Web.UI/config/ExamineSettings.config @@ -6,12 +6,12 @@ Index sets can be defined in the ExamineIndex.config if you're using the standar More information and documentation can be found on CodePlex: http://umbracoexamine.codeplex.com --> - + @@ -22,7 +22,7 @@ More information and documentation can be found on CodePlex: http://umbracoexami useTempStorage="Sync"/> - @@ -31,15 +31,15 @@ More information and documentation can be found on CodePlex: http://umbracoexami - From 8916c68a0c00896c2d20e170d329a419e39a59ea Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 18:35:29 +0100 Subject: [PATCH 084/406] reverts profiler changes, we'll inlcude in 7.4 --- .../Profiling/StartupWebProfilerProvider.cs | 126 ------------------ src/Umbraco.Core/Profiling/WebProfiler.cs | 48 ++----- src/Umbraco.Core/Umbraco.Core.csproj | 1 - 3 files changed, 11 insertions(+), 164 deletions(-) delete mode 100644 src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs diff --git a/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs b/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs deleted file mode 100644 index 16ce638c0e..0000000000 --- a/src/Umbraco.Core/Profiling/StartupWebProfilerProvider.cs +++ /dev/null @@ -1,126 +0,0 @@ -using System.Threading; -using System.Web; -using StackExchange.Profiling; - -namespace Umbraco.Core.Profiling -{ - /// - /// Allows us to profile items during app startup - before an HttpRequest is created - /// - internal class StartupWebProfilerProvider : WebRequestProfilerProvider - { - public StartupWebProfilerProvider() - { - _startupPhase = StartupPhase.Boot; - //create the startup profiler - _startupProfiler = new MiniProfiler("http://localhost/umbraco-startup", ProfileLevel.Verbose) - { - Name = "StartupProfiler" - }; - } - - private MiniProfiler _startupProfiler; - private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(); - - private enum StartupPhase - { - None = 0, - Boot = 1, - Request = 2 - } - - private volatile StartupPhase _startupPhase; - - public void BootComplete() - { - using (new ReadLock(_locker)) - { - if (_startupPhase != StartupPhase.Boot) return; - } - - using (var l = new UpgradeableReadLock(_locker)) - { - if (_startupPhase == StartupPhase.Boot) - { - l.UpgradeToWriteLock(); - - ////Now we need to transfer some information from our startup phase to the normal - ////web request phase to output the startup profiled information. - ////Stop our internal startup profiler, this will write out it's results to storage. - //StopProfiler(_startupProfiler); - //SaveProfiler(_startupProfiler); - - _startupPhase = StartupPhase.Request; - } - } - } - - public override void Stop(bool discardResults) - { - using (new ReadLock(_locker)) - { - if (_startupPhase == StartupPhase.None) - { - base.Stop(discardResults); - return; - } - } - - using (var l = new UpgradeableReadLock(_locker)) - { - if (_startupPhase > 0 && base.GetCurrentProfiler() == null) - { - l.UpgradeToWriteLock(); - - _startupPhase = StartupPhase.None; - - if (HttpContext.Current != null) - { - HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler; - base.Stop(discardResults); - _startupProfiler = null; - } - } - else - { - base.Stop(discardResults); - } - } - } - - public override MiniProfiler Start(ProfileLevel level) - { - using (new ReadLock(_locker)) - { - if (_startupPhase > 0 && base.GetCurrentProfiler() == null) - { - SetProfilerActive(_startupProfiler); - return _startupProfiler; - } - - return base.Start(level); - } - } - - public override MiniProfiler GetCurrentProfiler() - { - using (new ReadLock(_locker)) - { - if (_startupPhase > 0) - { - try - { - var current = base.GetCurrentProfiler(); - if (current == null) return _startupProfiler; - } - catch - { - return _startupProfiler; - } - } - - return base.GetCurrentProfiler(); - } - } - } -} \ No newline at end of file diff --git a/src/Umbraco.Core/Profiling/WebProfiler.cs b/src/Umbraco.Core/Profiling/WebProfiler.cs index 45d3a68591..00d088bca7 100644 --- a/src/Umbraco.Core/Profiling/WebProfiler.cs +++ b/src/Umbraco.Core/Profiling/WebProfiler.cs @@ -12,28 +12,16 @@ namespace Umbraco.Core.Profiling ///
    internal class WebProfiler : IProfiler { - private StartupWebProfilerProvider _startupWebProfilerProvider; /// /// Constructor - /// + ///
    + /// + /// Binds to application events to enable the MiniProfiler + /// internal WebProfiler() { - if (GlobalSettings.DebugMode) - { - //setup some defaults - MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); - MiniProfiler.Settings.StackMaxLength = 5000; - - //At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext - // since it hasn't started yet. So we need to do some hacking to enable profiling during startup. - _startupWebProfilerProvider = new StartupWebProfilerProvider(); - //this should always be the case during startup, we'll need to set a custom profiler provider - MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider; - - //Binds to application events to enable the MiniProfiler with a real HttpRequest - UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; - } + UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit; } ///
    @@ -65,12 +53,7 @@ namespace Umbraco.Core.Profiling /// void UmbracoApplicationEndRequest(object sender, EventArgs e) { - if (_startupWebProfilerProvider != null) - { - Stop(); - _startupWebProfilerProvider = null; - } - else if (CanPerformProfilingAction(sender)) + if (CanPerformProfilingAction(sender)) { Stop(); } @@ -83,11 +66,6 @@ namespace Umbraco.Core.Profiling /// void UmbracoApplicationBeginRequest(object sender, EventArgs e) { - if (_startupWebProfilerProvider != null) - { - _startupWebProfilerProvider.BootComplete(); - } - if (CanPerformProfilingAction(sender)) { Start(); @@ -126,7 +104,7 @@ namespace Umbraco.Core.Profiling /// public string Render() { - return GlobalSettings.DebugMode ? MiniProfiler.RenderIncludes(RenderPosition.Right).ToString() : string.Empty; + return MiniProfiler.RenderIncludes(RenderPosition.Right).ToString(); } /// @@ -147,10 +125,9 @@ namespace Umbraco.Core.Profiling /// public void Start() { - if (GlobalSettings.DebugMode) - { - MiniProfiler.Start(); - } + MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter(); + MiniProfiler.Settings.StackMaxLength = 5000; + MiniProfiler.Start(); } /// @@ -162,10 +139,7 @@ namespace Umbraco.Core.Profiling /// public void Stop(bool discardResults = false) { - if (GlobalSettings.DebugMode) - { - MiniProfiler.Stop(discardResults); - } + MiniProfiler.Stop(discardResults); } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index d11270592a..2ef0501f8e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -469,7 +469,6 @@ - From d1be38be64b7e9150a9f993596bebb06f2db145a Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 18:37:56 +0100 Subject: [PATCH 085/406] reverts profiler changes, we'll inlcude in 7.4 --- src/Umbraco.Web/WebBootManager.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Umbraco.Web/WebBootManager.cs b/src/Umbraco.Web/WebBootManager.cs index 3ca8903c98..67e0e65703 100644 --- a/src/Umbraco.Web/WebBootManager.cs +++ b/src/Umbraco.Web/WebBootManager.cs @@ -182,11 +182,8 @@ namespace Umbraco.Web protected override void InitializeProfilerResolver() { base.InitializeProfilerResolver(); - //Set the profiler to be the web profiler - var profiler = new WebProfiler(); - ProfilerResolver.Current.SetProfiler(profiler); - profiler.Start(); + ProfilerResolver.Current.SetProfiler(new WebProfiler()); } /// From e2bf27e53fdc8649e1981f1c0cc2f73600f4882d Mon Sep 17 00:00:00 2001 From: Shannon Date: Thu, 28 Jan 2016 19:35:11 +0100 Subject: [PATCH 086/406] U4-7857 Flexible Load Balancing does not sync with the correct timeout threshold --- src/Umbraco.Core/Sync/DatabaseServerMessenger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index a4b4d35d10..a27b114f4d 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -175,7 +175,7 @@ namespace Umbraco.Core.Sync if (_released) return; - if ((DateTime.UtcNow - _lastSync).Seconds <= _options.ThrottleSeconds) + if ((DateTime.UtcNow - _lastSync).TotalSeconds <= _options.ThrottleSeconds) return; _syncing = true; From b6962296c3fa8db3e564f259d1f2af0d3b50f16b Mon Sep 17 00:00:00 2001 From: Arnoud de Vries Date: Sat, 30 Jan 2016 15:51:17 +0100 Subject: [PATCH 087/406] Fixes: U4-7687 Create dialog briefly shows 'no allowed document types' text --- src/Umbraco.Web.UI.Client/src/views/content/create.html | 4 ++-- src/Umbraco.Web.UI.Client/src/views/datatypes/create.html | 2 +- src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html | 2 +- src/Umbraco.Web.UI.Client/src/views/mediatypes/create.html | 2 +- src/Umbraco.Web.UI.Client/src/views/member/create.html | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/content/create.html b/src/Umbraco.Web.UI.Client/src/views/content/create.html index d3bc28bc55..c40fb39351 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/create.html @@ -1,9 +1,9 @@ - public static event TypedEventHandler Migrated; } -} \ No newline at end of file +} From f4e6913e6ecdafaf66316b97a667777520576e35 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 1 Feb 2016 15:14:38 +0100 Subject: [PATCH 092/406] Fixes: U4-7832 double data bindings --- .../components/umblockedfield.directive.js | 16 ---------------- .../components/validation/valregex.directive.js | 17 +++++++++++++++-- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js index b382fa1c32..204fa7bb86 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umblockedfield.directive.js @@ -12,22 +12,6 @@ function LockedFieldDirective($timeout, localizationService) { function link(scope, el, attr, ngModelCtrl) { - - //watch the ngModel so we can manually update the textbox view value when it changes - // this ensures that the normal flow (i.e. a user editing the text box) occurs so that - // the parsers, validators and viewchangelisteners execute - scope.$watch("ngModel", function (newValue, oldValue) { - if (newValue !== oldValue) { - //Hack: in order for the pipeline to execute for setViewValue, the underlying $modelValue cannot - // match the value being set with the newValue, so we'll se it to undefined first. - // We could avoid this hack by setting the ngModel of the lockedField input field to a custom - // scope object, but that would mean we'd have to watch that value too in order to set the outer - // ngModelCtrl.$modelValue. It's seems like less overhead to just do this and not have 2x watches. - scope.lockedFieldForm.lockedField.$modelValue = undefined; - scope.lockedFieldForm.lockedField.$render(); - } - scope.lockedFieldForm.lockedField.$setViewValue(scope.lockedFieldForm.lockedField.$modelValue); - }); function activate() { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js index bc69d1cd02..7bc3c6b877 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/validation/valregex.directive.js @@ -14,6 +14,7 @@ function valRegex() { var flags = ""; var regex; + var eventBindings = []; attrs.$observe("valRegexFlags", function (newVal) { if (newVal) { @@ -38,6 +39,12 @@ function valRegex() { } }); + eventBindings.push(scope.$watch('ngModel', function(newValue, oldValue){ + if(newValue && newValue !== oldValue) { + patternValidator(newValue); + } + })); + var patternValidator = function (viewValue) { if (regex) { //NOTE: we don't validate on empty values, use required validator for that @@ -58,8 +65,14 @@ function valRegex() { } }; - ctrl.$parsers.push(patternValidator); + scope.$on('$destroy', function(){ + // unbind watchers + for(var e in eventBindings) { + eventBindings[e](); + } + }); + } }; } -angular.module('umbraco.directives.validation').directive("valRegex", valRegex); \ No newline at end of file +angular.module('umbraco.directives.validation').directive("valRegex", valRegex); From b22ae28acc7b1abb9460d34e663198fb0c91ca5e Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 1 Feb 2016 18:24:30 +0100 Subject: [PATCH 093/406] Fixes: U4-7871 Media picker - add functionality to go to media item when an image has been picked --- .../src/less/property-editors.less | 41 +++++++++++++++++-- .../mediapicker/mediapicker.controller.js | 6 ++- .../mediapicker/mediapicker.html | 21 +++++++--- 3 files changed, 57 insertions(+), 11 deletions(-) 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 b3cb034596..7ea8cfe949 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -200,10 +200,6 @@ ul.color-picker li a { border: 1px solid #f8f8f8; } -.umb-sortable-thumbnails li:hover a { - display: block; -} - .umb-sortable-thumbnails li img { max-width:100%; max-height:100%; @@ -228,6 +224,43 @@ ul.color-picker li a { display: block; } +.umb-sortable-thumbnails .umb-sortable-thumbnails__actions { + position: absolute; + bottom: 10px; + right: 10px; + text-decoration: none; + display: flex; + flex-direction: row; + opacity: 0; + visibility: hidden; +} + +.umb-sortable-thumbnails li:hover .umb-sortable-thumbnails__actions { + opacity: 1; + visibility: visible; +} + +.umb-sortable-thumbnails .umb-sortable-thumbnails__action { + font-size: 16px; + background: white; + height: 25px; + width: 25px; + border-radius: 15px; + color: @grayDarker; + display: flex; + justify-content: center; + align-items: center; + margin-left: 5px; +} + +.umb-sortable-thumbnails .umb-sortable-thumbnails__action.-red { + color: red; +} + +.umb-sortable-thumbnails .umb-sortable-thumbnails__action:hover { + text-decoration: none; +} + // // Cropper 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 f73920a377..bf82efae24 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 @@ -1,7 +1,7 @@ //this controller simply tells the dialogs service to open a mediaPicker window //with a specified callback, this callback will receive an object with a selection on it angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerController", - function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService) { + function ($rootScope, $scope, dialogService, entityResource, mediaResource, mediaHelper, $timeout, userService, $location) { //check the pre-values for multi-picker var multiPicker = $scope.model.config.multiPicker && $scope.model.config.multiPicker !== '0' ? true : false; @@ -56,6 +56,10 @@ angular.module('umbraco').controller("Umbraco.PropertyEditors.MediaPickerControl $scope.sync(); }; + $scope.goToItem = function(item) { + $location.path('media/media/edit/' + item.id); + }; + $scope.add = function() { $scope.mediaPickerOverlay = { 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 9af1c7b10d..096685f1a9 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 @@ -2,14 +2,23 @@
    • - - - - {{image.name}} - + + + + + {{image.name}} + + + -
    From 1abab419556fc11e42753b11a304ef5f2a848259 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Feb 2016 21:45:34 +0100 Subject: [PATCH 094/406] Fixes perf issue with DeepCloneHelper - so we cache the actual property types instead of re-reflecting each time --- src/Umbraco.Core/Models/DeepCloneHelper.cs | 160 +++++++++++++-------- 1 file changed, 99 insertions(+), 61 deletions(-) diff --git a/src/Umbraco.Core/Models/DeepCloneHelper.cs b/src/Umbraco.Core/Models/DeepCloneHelper.cs index 7523555c24..c1b45f63ce 100644 --- a/src/Umbraco.Core/Models/DeepCloneHelper.cs +++ b/src/Umbraco.Core/Models/DeepCloneHelper.cs @@ -9,10 +9,30 @@ namespace Umbraco.Core.Models { public static class DeepCloneHelper { + /// + /// Stores the metadata for the properties for a given type so we know how to create them + /// + private struct ClonePropertyInfo + { + public ClonePropertyInfo(PropertyInfo propertyInfo) : this() + { + if (propertyInfo == null) throw new ArgumentNullException("propertyInfo"); + PropertyInfo = propertyInfo; + } + + public PropertyInfo PropertyInfo { get; private set; } + public bool IsDeepCloneable { get; set; } + public Type GenericListType { get; set; } + public bool IsList + { + get { return GenericListType != null; } + } + } + /// /// Used to avoid constant reflection (perf) /// - private static readonly ConcurrentDictionary PropCache = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary PropCache = new ConcurrentDictionary(); /// /// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output') @@ -30,81 +50,99 @@ namespace Umbraco.Core.Models throw new InvalidOperationException("Both the input and output types must be the same"); } + //get the property metadata from cache so we only have to figure this out once per type var refProperties = PropCache.GetOrAdd(inputType, type => inputType.GetProperties() - .Where(x => - //is not attributed with the ignore clone attribute - x.GetCustomAttribute() == null + .Select(propertyInfo => + { + if ( + //is not attributed with the ignore clone attribute + propertyInfo.GetCustomAttribute() != null //reference type but not string - && x.PropertyType.IsValueType == false && x.PropertyType != typeof (string) + || propertyInfo.PropertyType.IsValueType || propertyInfo.PropertyType == typeof (string) //settable - && x.CanWrite + || propertyInfo.CanWrite == false //non-indexed - && x.GetIndexParameters().Any() == false) + || propertyInfo.GetIndexParameters().Any()) + { + return null; + } + + + if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)) + { + return new ClonePropertyInfo(propertyInfo) { IsDeepCloneable = true }; + } + + if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) + && TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) == false) + { + if (propertyInfo.PropertyType.IsGenericType + && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) + || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))) + { + //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> + var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); + return new ClonePropertyInfo(propertyInfo) { GenericListType = genericType }; + } + if (propertyInfo.PropertyType.IsArray + || (propertyInfo.PropertyType.IsInterface && propertyInfo.PropertyType.IsGenericType == false)) + { + //if its an array, we'll create a list to work with first and then convert to array later + //otherwise if its just a regular derivitave of IEnumerable, we can use a list too + return new ClonePropertyInfo(propertyInfo) { GenericListType = typeof(List) }; + } + //skip instead of trying to create instance of abstract or interface + if (propertyInfo.PropertyType.IsAbstract || propertyInfo.PropertyType.IsInterface) + { + return null; + } + + //its a custom IEnumerable, we'll try to create it + try + { + var custom = Activator.CreateInstance(propertyInfo.PropertyType); + //if it's an IList we can work with it, otherwise we cannot + var newList = custom as IList; + if (newList == null) + { + return null; + } + return new ClonePropertyInfo(propertyInfo) {GenericListType = propertyInfo.PropertyType}; + } + catch (Exception) + { + //could not create this type so we'll skip it + return null; + } + } + return new ClonePropertyInfo(propertyInfo); + }) + .Where(x => x.HasValue) + .Select(x => x.Value) .ToArray()); - foreach (var propertyInfo in refProperties) + foreach (var clonePropertyInfo in refProperties) { - if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType)) + if (clonePropertyInfo.IsDeepCloneable) { //this ref property is also deep cloneable so clone it - var result = (IDeepCloneable)propertyInfo.GetValue(input, null); + var result = (IDeepCloneable)clonePropertyInfo.PropertyInfo.GetValue(input, null); if (result != null) { //set the cloned value to the property - propertyInfo.SetValue(output, result.DeepClone(), null); + clonePropertyInfo.PropertyInfo.SetValue(output, result.DeepClone(), null); } } - else if (TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) - && TypeHelper.IsTypeAssignableFrom(propertyInfo.PropertyType) == false) + else if (clonePropertyInfo.IsList) { - IList newList; - if (propertyInfo.PropertyType.IsGenericType - && (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>) - || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>) - || propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>))) - { - //if it is a IEnumerable<>, IList or ICollection<> we'll use a List<> - var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments()); - newList = (IList)Activator.CreateInstance(genericType); - } - else if (propertyInfo.PropertyType.IsArray - || (propertyInfo.PropertyType.IsInterface && propertyInfo.PropertyType.IsGenericType == false)) - { - //if its an array, we'll create a list to work with first and then convert to array later - //otherwise if its just a regular derivitave of IEnumerable, we can use a list too - newList = new List(); - } - else - { - //skip instead of trying to create instance of abstract or interface - if (propertyInfo.PropertyType.IsAbstract || propertyInfo.PropertyType.IsInterface) - { - continue; - } - - //its a custom IEnumerable, we'll try to create it - try - { - var custom = Activator.CreateInstance(propertyInfo.PropertyType); - //if it's an IList we can work with it, otherwise we cannot - newList = custom as IList; - if (newList == null) - { - continue; - } - } - catch (Exception) - { - //could not create this type so we'll skip it - continue; - } - } - - var enumerable = (IEnumerable)propertyInfo.GetValue(input, null); + var enumerable = (IEnumerable)clonePropertyInfo.PropertyInfo.GetValue(input, null); if (enumerable == null) continue; + var newList = (IList)Activator.CreateInstance(clonePropertyInfo.GenericListType); + var isUsableType = true; //now clone each item @@ -136,21 +174,21 @@ namespace Umbraco.Core.Models continue; } - if (propertyInfo.PropertyType.IsArray) + if (clonePropertyInfo.PropertyInfo.PropertyType.IsArray) { //need to convert to array - var arr = (object[])Activator.CreateInstance(propertyInfo.PropertyType, newList.Count); + var arr = (object[])Activator.CreateInstance(clonePropertyInfo.PropertyInfo.PropertyType, newList.Count); for (int i = 0; i < newList.Count; i++) { arr[i] = newList[i]; } //set the cloned collection - propertyInfo.SetValue(output, arr, null); + clonePropertyInfo.PropertyInfo.SetValue(output, arr, null); } else { //set the cloned collection - propertyInfo.SetValue(output, newList, null); + clonePropertyInfo.PropertyInfo.SetValue(output, newList, null); } } From 1dea0edcf18887a55ca41cd9c6714432244c2ed7 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 1 Feb 2016 22:50:38 +0100 Subject: [PATCH 095/406] re-includes the static cache for published property types, this cache is much more than a simple cache of content type infos, it is also the cache for associated converters which is required for all front-end rendering. Fixes the issue of not setting the xpath cache level corectly. --- .../PublishedContent/PublishedContentType.cs | 58 +++++++++++++++++-- .../PublishedContent/PublishedPropertyType.cs | 4 +- .../Cache/ContentTypeCacheRefresher.cs | 8 ++- .../Cache/DataTypeCacheRefresher.cs | 1 + 4 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs index 3cff4f0298..5f30c08ce7 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedContentType.cs @@ -98,10 +98,45 @@ namespace Umbraco.Core.Models.PublishedContent #endregion - + #region Cache + + // these methods are called by ContentTypeCacheRefresher and DataTypeCacheRefresher + + internal static void ClearAll() + { + Logging.LogHelper.Debug("Clear all."); + // ok and faster to do it by types, assuming noone else caches PublishedContentType instances + //ApplicationContext.Current.ApplicationCache.ClearStaticCacheByKeySearch("PublishedContentType_"); + ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes(); + } + + internal static void ClearContentType(int id) + { + Logging.LogHelper.Debug("Clear content type w/id {0}.", () => id); + // requires a predicate because the key does not contain the ID + // faster than key strings comparisons anyway + ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( + (key, value) => value.Id == id); + } + + internal static void ClearDataType(int id) + { + Logging.LogHelper.Debug("Clear data type w/id {0}.", () => id); + // there is no recursion to handle here because a PublishedContentType contains *all* its + // properties ie both its own properties and those that were inherited (it's based upon an + // IContentTypeComposition) and so every PublishedContentType having a property based upon + // the cleared data type, be it local or inherited, will be cleared. + ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheObjectTypes( + (key, value) => value.PropertyTypes.Any(x => x.DataTypeId == id)); + } + public static PublishedContentType Get(PublishedItemType itemType, string alias) { - var type = CreatePublishedContentType(itemType, alias); + var key = string.Format("PublishedContentType_{0}_{1}", + itemType.ToString().ToLowerInvariant(), alias.ToLowerInvariant()); + + var type = ApplicationContext.Current.ApplicationCache.StaticCache.GetCacheItem(key, + () => CreatePublishedContentType(itemType, alias)); return type; } @@ -134,8 +169,21 @@ namespace Umbraco.Core.Models.PublishedContent return new PublishedContentType(contentType); } - // for unit tests - internal static Func GetPublishedContentTypeCallback { get; set; } - + // for unit tests - changing the callback must reset the cache obviously + private static Func _getPublishedContentTypeCallBack; + internal static Func GetPublishedContentTypeCallback + { + get { return _getPublishedContentTypeCallBack; } + set + { + // see note above + //ClearAll(); + ApplicationContext.Current.ApplicationCache.StaticCache.ClearCacheByKeySearch("PublishedContentType_"); + + _getPublishedContentTypeCallBack = value; + } + } + + #endregion } } diff --git a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs index f4b1597a7d..22d453e150 100644 --- a/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs +++ b/src/Umbraco.Core/Models/PublishedContent/PublishedPropertyType.cs @@ -230,13 +230,13 @@ namespace Umbraco.Core.Models.PublishedContent { _sourceCacheLevel = converterMeta.GetPropertyCacheLevel(this, PropertyCacheValue.Source); _objectCacheLevel = converterMeta.GetPropertyCacheLevel(this, PropertyCacheValue.Object); - _objectCacheLevel = converterMeta.GetPropertyCacheLevel(this, PropertyCacheValue.XPath); + _xpathCacheLevel = converterMeta.GetPropertyCacheLevel(this, PropertyCacheValue.XPath); } else { _sourceCacheLevel = GetCacheLevel(_converter, PropertyCacheValue.Source); _objectCacheLevel = GetCacheLevel(_converter, PropertyCacheValue.Object); - _objectCacheLevel = GetCacheLevel(_converter, PropertyCacheValue.XPath); + _xpathCacheLevel = GetCacheLevel(_converter, PropertyCacheValue.XPath); } if (_objectCacheLevel < _sourceCacheLevel) _objectCacheLevel = _sourceCacheLevel; if (_xpathCacheLevel < _sourceCacheLevel) _xpathCacheLevel = _sourceCacheLevel; diff --git a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs index c44b3f2b51..44a6efe9ff 100644 --- a/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/ContentTypeCacheRefresher.cs @@ -141,7 +141,9 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.ContentTypeCacheKey); //clear static object cache global::umbraco.cms.businesslogic.ContentType.RemoveAllDataTypeCache(); - + + PublishedContentType.ClearAll(); + base.RefreshAll(); } @@ -278,7 +280,9 @@ namespace Umbraco.Web.Cache //clears the dictionary object cache of the legacy ContentType global::umbraco.cms.businesslogic.ContentType.RemoveFromDataTypeCache(payload.Alias); - + + PublishedContentType.ClearContentType(payload.Id); + //need to recursively clear the cache for each child content type foreach (var descendant in payload.DescendantPayloads) { diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index 173f4dcb86..11b3ab6294 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -108,6 +108,7 @@ namespace Umbraco.Web.Cache if (dataTypeCache) dataTypeCache.Result.ClearCacheByKeySearch(string.Format("{0}{1}", CacheKeys.DataTypePreValuesCacheKey, payload.Id)); + PublishedContentType.ClearDataType(payload.Id); }); base.Refresh(jsonPayload); From 6e27b3d6d45c4693168cce4a2be868fe5bd75256 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 2 Feb 2016 00:47:18 +0100 Subject: [PATCH 096/406] Fixes up the FullDataSetRepositoryCachePolicy to handle individual items, updates and removes correctly, splits up it's logic so it's not overriding the DefaultRepositoryCachePolicy since that is just different. Adds tests. --- .../Cache/DefaultRepositoryCachePolicy.cs | 78 ++++----- .../Cache/FullDataSetRepositoryCachePolicy.cs | 163 +++++++++++++++--- ...FullDataSetRepositoryCachePolicyFactory.cs | 9 +- .../Cache/IRepositoryCachePolicy.cs | 4 +- .../Cache/RepositoryCachePolicyBase.cs | 48 ++++++ .../SingleItemsOnlyRepositoryCachePolicy.cs | 2 +- .../Repositories/ContentTypeRepository.cs | 5 +- .../Repositories/DomainRepository.cs | 3 +- .../Repositories/LanguageRepository.cs | 3 +- .../Repositories/MediaTypeRepository.cs | 5 +- .../Repositories/MemberTypeRepository.cs | 5 +- .../Repositories/PublicAccessRepository.cs | 3 +- .../Repositories/TemplateRepository.cs | 3 +- src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../Cache/DefaultCachePolicyTests.cs | 32 ++++ .../Cache/FullDataSetCachePolicyTests.cs | 154 +++++++++++++++-- 16 files changed, 420 insertions(+), 98 deletions(-) create mode 100644 src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs diff --git a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs index 45e79a1b67..1f51fc3ccc 100644 --- a/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/DefaultRepositoryCachePolicy.cs @@ -15,35 +15,31 @@ namespace Umbraco.Core.Cache /// This cache policy uses sliding expiration and caches instances for 5 minutes. However if allow zero count is true, then we use the /// default policy with no expiry. /// - internal class DefaultRepositoryCachePolicy : DisposableObject, IRepositoryCachePolicy + internal class DefaultRepositoryCachePolicy : RepositoryCachePolicyBase where TEntity : class, IAggregateRoot { private readonly RepositoryCachePolicyOptions _options; - protected IRuntimeCacheProvider Cache { get; private set; } - private Action _action; public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) - { - if (cache == null) throw new ArgumentNullException("cache"); + : base(cache) + { if (options == null) throw new ArgumentNullException("options"); - - _options = options; - Cache = cache; + _options = options; } - public string GetCacheIdKey(object id) + protected string GetCacheIdKey(object id) { if (id == null) throw new ArgumentNullException("id"); return string.Format("{0}{1}", GetCacheTypeKey(), id); } - public string GetCacheTypeKey() + protected string GetCacheTypeKey() { return string.Format("uRepo_{0}_", typeof(TEntity).Name); } - public void CreateOrUpdate(TEntity entity, Action persistMethod) + public override void CreateOrUpdate(TEntity entity, Action persistMethod) { if (entity == null) throw new ArgumentNullException("entity"); if (persistMethod == null) throw new ArgumentNullException("persistMethod"); @@ -85,24 +81,29 @@ namespace Umbraco.Core.Cache } } - public void Remove(TEntity entity, Action persistMethod) + public override void Remove(TEntity entity, Action persistMethod) { if (entity == null) throw new ArgumentNullException("entity"); if (persistMethod == null) throw new ArgumentNullException("persistMethod"); - persistMethod(entity); - - //set the disposal action - var cacheKey = GetCacheIdKey(entity.Id); - SetCacheAction(() => + try { - Cache.ClearCacheItem(cacheKey); - //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared - Cache.ClearCacheItem(GetCacheTypeKey()); - }); + persistMethod(entity); + } + finally + { + //set the disposal action + var cacheKey = GetCacheIdKey(entity.Id); + SetCacheAction(() => + { + Cache.ClearCacheItem(cacheKey); + //If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared + Cache.ClearCacheItem(GetCacheTypeKey()); + }); + } } - public TEntity Get(TId id, Func getFromRepo) + public override TEntity Get(TId id, Func getFromRepo) { if (getFromRepo == null) throw new ArgumentNullException("getFromRepo"); @@ -119,13 +120,13 @@ namespace Umbraco.Core.Cache return entity; } - public TEntity Get(TId id) + public override TEntity Get(TId id) { var cacheKey = GetCacheIdKey(id); return Cache.GetCacheItem(cacheKey); } - public bool Exists(TId id, Func getFromRepo) + public override bool Exists(TId id, Func getFromRepo) { if (getFromRepo == null) throw new ArgumentNullException("getFromRepo"); @@ -134,7 +135,7 @@ namespace Umbraco.Core.Cache return fromCache != null || getFromRepo(id); } - public virtual TEntity[] GetAll(TId[] ids, Func> getFromRepo) + public override TEntity[] GetAll(TId[] ids, Func> getFromRepo) { if (getFromRepo == null) throw new ArgumentNullException("getFromRepo"); @@ -188,7 +189,7 @@ namespace Umbraco.Core.Cache /// Looks up the zero count cache, must return null if it doesn't exist /// /// - protected virtual bool HasZeroCountCache() + protected bool HasZeroCountCache() { var zeroCount = Cache.GetCacheItem(GetCacheTypeKey()); return (zeroCount != null && zeroCount.Any() == false); @@ -198,24 +199,13 @@ namespace Umbraco.Core.Cache /// Performs the lookup for all entities of this type from the cache /// /// - protected virtual TEntity[] GetAllFromCache() + protected TEntity[] GetAllFromCache() { var allEntities = Cache.GetCacheItemsByKeySearch(GetCacheTypeKey()) .WhereNotNull() .ToArray(); return allEntities.Any() ? allEntities : new TEntity[] {}; - } - - /// - /// The disposal performs the caching - /// - protected override void DisposeResources() - { - if (_action != null) - { - _action(); - } - } + } /// /// Sets the action to execute on disposal for a single entity @@ -273,14 +263,6 @@ namespace Umbraco.Core.Cache } }); } - - /// - /// Sets the action to execute on disposal - /// - /// - protected void SetCacheAction(Action action) - { - _action = action; - } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index c098af8992..eeb651dc09 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -11,33 +11,114 @@ namespace Umbraco.Core.Cache /// /// /// - /// - /// This caching policy has no sliding expiration but uses the default ObjectCache.InfiniteAbsoluteExpiration as it's timeout, so it - /// should not leave the cache unless the cache memory is exceeded and it gets thrown out. - /// - internal class FullDataSetRepositoryCachePolicy : DefaultRepositoryCachePolicy + internal class FullDataSetRepositoryCachePolicy : RepositoryCachePolicyBase where TEntity : class, IAggregateRoot { private readonly Func _getEntityId; + private readonly Func> _getAllFromRepo; + private readonly bool _expires; - public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func getEntityId) : base(cache, - new RepositoryCachePolicyOptions - { - //Definitely allow zero'd cache entires since this is a full set, in many cases there will be none, - // and we must cache this! - GetAllCacheAllowZeroCount = true - }) + public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func getEntityId, Func> getAllFromRepo, bool expires) + : base(cache) { _getEntityId = getEntityId; + _getAllFromRepo = getAllFromRepo; + _expires = expires; } private bool? _hasZeroCountCache; + protected string GetCacheTypeKey() + { + return string.Format("uRepo_{0}_", typeof(TEntity).Name); + } + + public override void CreateOrUpdate(TEntity entity, Action persistMethod) + { + if (entity == null) throw new ArgumentNullException("entity"); + if (persistMethod == null) throw new ArgumentNullException("persistMethod"); + + try + { + persistMethod(entity); + + //set the disposal action + SetCacheAction(() => + { + //Clear all + Cache.ClearCacheItem(GetCacheTypeKey()); + }); + } + catch + { + //set the disposal action + SetCacheAction(() => + { + //Clear all + Cache.ClearCacheItem(GetCacheTypeKey()); + }); + throw; + } + } + + public override void Remove(TEntity entity, Action persistMethod) + { + if (entity == null) throw new ArgumentNullException("entity"); + if (persistMethod == null) throw new ArgumentNullException("persistMethod"); + + try + { + persistMethod(entity); + } + finally + { + //set the disposal action + SetCacheAction(() => + { + //Clear all + Cache.ClearCacheItem(GetCacheTypeKey()); + }); + } + } + + public override TEntity Get(TId id, Func getFromRepo) + { + //Force get all with cache + var found = GetAll(new TId[] { }, ids => _getAllFromRepo()); + + //we don't have anything in cache (this should never happen), just return from the repo + return found == null + ? getFromRepo(id) + : found.FirstOrDefault(x => _getEntityId(x).Equals(id)); + } + + public override TEntity Get(TId id) + { + //Force get all with cache + var found = GetAll(new TId[] { }, ids => _getAllFromRepo()); + + //we don't have anything in cache (this should never happen), just return null + return found == null + ? null + : found.FirstOrDefault(x => _getEntityId(x).Equals(id)); + } + + public override bool Exists(TId id, Func getFromRepo) + { + //Force get all with cache + var found = GetAll(new TId[] {}, ids => _getAllFromRepo()); + + //we don't have anything in cache (this should never happen), just return from the repo + return found == null + ? getFromRepo(id) + : found.Any(x => _getEntityId(x).Equals(id)); + } + public override TEntity[] GetAll(TId[] ids, Func> getFromRepo) { - //process the base logic without any Ids - we want to cache them all! - var result = base.GetAll(new TId[] { }, getFromRepo); + //process getting all including setting the cache callback + var result = PerformGetAll(getFromRepo); //now that the base result has been calculated, they will all be cached. // Now we can just filter by ids if they have been supplied @@ -47,31 +128,64 @@ namespace Umbraco.Core.Cache : result; } + protected TEntity[] PerformGetAll(Func> getFromRepo) + { + var allEntities = GetAllFromCache(); + if (allEntities.Any()) + { + return allEntities; + } + + //check the zero count cache + if (HasZeroCountCache()) + { + //there is a zero count cache so return an empty list + return new TEntity[] { }; + } + + //we need to do the lookup from the repo + var entityCollection = getFromRepo(new TId[] {}) + //ensure we don't include any null refs in the returned collection! + .WhereNotNull() + .ToArray(); + + //set the disposal action + SetCacheAction(entityCollection); + + return entityCollection; + } + /// /// For this type of caching policy, we don't cache individual items /// /// /// - protected override void SetCacheAction(string cacheKey, TEntity entity) + protected void SetCacheAction(string cacheKey, TEntity entity) { - //do nothing + //No-op } /// /// Sets the action to execute on disposal for an entity collection /// - /// /// - protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection) + protected void SetCacheAction(TEntity[] entityCollection) { - //for this type of caching policy, we don't want to cache any GetAll request containing specific Ids - if (ids.Any()) return; - //set the disposal action SetCacheAction(() => { //We want to cache the result as a single collection - Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection)); + + if (_expires) + { + Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection), + timeout: TimeSpan.FromMinutes(5), + isSliding: true); + } + else + { + Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList(entityCollection)); + } }); } @@ -79,7 +193,7 @@ namespace Umbraco.Core.Cache /// Looks up the zero count cache, must return null if it doesn't exist /// /// - protected override bool HasZeroCountCache() + protected bool HasZeroCountCache() { if (_hasZeroCountCache.HasValue) return _hasZeroCountCache.Value; @@ -92,7 +206,7 @@ namespace Umbraco.Core.Cache /// This policy will cache the full data set as a single collection /// /// - protected override TEntity[] GetAllFromCache() + protected TEntity[] GetAllFromCache() { var found = Cache.GetCacheItem>(GetCacheTypeKey()); @@ -101,5 +215,6 @@ namespace Umbraco.Core.Cache return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray(); } + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs index 6a79c2b8c2..e4addcf355 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicyFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Cache @@ -13,16 +14,20 @@ namespace Umbraco.Core.Cache { private readonly IRuntimeCacheProvider _runtimeCache; private readonly Func _getEntityId; + private readonly Func> _getAllFromRepo; + private readonly bool _expires; - public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, Func getEntityId) + public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, Func getEntityId, Func> getAllFromRepo, bool expires) { _runtimeCache = runtimeCache; _getEntityId = getEntityId; + _getAllFromRepo = getAllFromRepo; + _expires = expires; } public virtual IRepositoryCachePolicy CreatePolicy() { - return new FullDataSetRepositoryCachePolicy(_runtimeCache, _getEntityId); + return new FullDataSetRepositoryCachePolicy(_runtimeCache, _getEntityId, _getAllFromRepo, _expires); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs index 97844933b7..215487c3be 100644 --- a/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/IRepositoryCachePolicy.cs @@ -10,9 +10,7 @@ namespace Umbraco.Core.Cache TEntity Get(TId id, Func getFromRepo); TEntity Get(TId id); bool Exists(TId id, Func getFromRepo); - - string GetCacheIdKey(object id); - string GetCacheTypeKey(); + void CreateOrUpdate(TEntity entity, Action persistMethod); void Remove(TEntity entity, Action persistMethod); TEntity[] GetAll(TId[] ids, Func> getFromRepo); diff --git a/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs new file mode 100644 index 0000000000..b939cd14e6 --- /dev/null +++ b/src/Umbraco.Core/Cache/RepositoryCachePolicyBase.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models.EntityBase; + +namespace Umbraco.Core.Cache +{ + internal abstract class RepositoryCachePolicyBase : DisposableObject, IRepositoryCachePolicy + where TEntity : class, IAggregateRoot + { + private Action _action; + + protected RepositoryCachePolicyBase(IRuntimeCacheProvider cache) + { + if (cache == null) throw new ArgumentNullException("cache"); + + Cache = cache; + } + + protected IRuntimeCacheProvider Cache { get; private set; } + + /// + /// The disposal performs the caching + /// + protected override void DisposeResources() + { + if (_action != null) + { + _action(); + } + } + + /// + /// Sets the action to execute on disposal + /// + /// + protected void SetCacheAction(Action action) + { + _action = action; + } + + public abstract TEntity Get(TId id, Func getFromRepo); + public abstract TEntity Get(TId id); + public abstract bool Exists(TId id, Func getFromRepo); + public abstract void CreateOrUpdate(TEntity entity, Action persistMethod); + public abstract void Remove(TEntity entity, Action persistMethod); + public abstract TEntity[] GetAll(TId[] ids, Func> getFromRepo); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs index 9566cd6e7f..28ac4ee2d1 100644 --- a/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/SingleItemsOnlyRepositoryCachePolicy.cs @@ -18,7 +18,7 @@ namespace Umbraco.Core.Cache protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection) { - //do nothing + //no-op } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 1a09a2206d..1441db0907 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -34,7 +34,10 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), + //allow this cache to expire + expires:true)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs index 563243f12c..7b6cc162a8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DomainRepository.cs @@ -29,7 +29,8 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), false)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index 3884eac888..f9a8e59cfa 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -30,7 +30,8 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), false)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 4ee5e1a327..2651cf98d4 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -31,7 +31,10 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), + //allow this cache to expire + expires: true)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs index 4bfdbf3c8a..ddbf08e71b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberTypeRepository.cs @@ -33,7 +33,10 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), + //allow this cache to expire + expires: true)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs index 1d8e56190b..22fad9d99b 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PublicAccessRepository.cs @@ -26,7 +26,8 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), false)); } } diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index a523a06293..fa780e1bd0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -51,7 +51,8 @@ namespace Umbraco.Core.Persistence.Repositories get { //Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection - return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory(RuntimeCache, GetEntityId)); + return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory( + RuntimeCache, GetEntityId, () => PerformGetAll(), false)); } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 2ef0501f8e..e0991e9ba7 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -175,6 +175,7 @@ + diff --git a/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs index 32381b593b..9b0aaac78b 100644 --- a/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/DefaultCachePolicyTests.cs @@ -120,5 +120,37 @@ namespace Umbraco.Tests.Cache Assert.IsTrue(cacheCleared); } } + + [Test] + public void If_Removes_Throws_Cache_Is_Removed() + { + var cacheCleared = false; + var cache = new Mock(); + cache.Setup(x => x.ClearCacheItem(It.IsAny())) + .Callback(() => + { + cacheCleared = true; + }); + + var defaultPolicy = new DefaultRepositoryCachePolicy(cache.Object, new RepositoryCachePolicyOptions()); + try + { + using (defaultPolicy) + { + defaultPolicy.Remove(new AuditItem(1, "blah", AuditType.Copy, 123), item => + { + throw new Exception("blah!"); + }); + } + } + catch + { + //we need this catch or nunit throw up + } + finally + { + Assert.IsTrue(cacheCleared); + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs index 9187fe5b27..d3df319ac7 100644 --- a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -14,9 +14,57 @@ namespace Umbraco.Tests.Cache [TestFixture] public class FullDataSetCachePolicyTests { + [Test] + public void Caches_Single() + { + var getAll = new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }; + + var isCached = false; + var cache = new Mock(); + cache.Setup(x => x.InsertCacheItem(It.IsAny(), It.IsAny>(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())) + .Callback(() => + { + isCached = true; + }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); + using (defaultPolicy) + { + var found = defaultPolicy.Get(1, o => new AuditItem(1, "blah", AuditType.Copy, 123)); + } + Assert.IsTrue(isCached); + } + + [Test] + public void Get_Single_From_Cache() + { + var getAll = new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }; + + var cache = new Mock(); + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem(1, "blah", AuditType.Copy, 123)); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); + using (defaultPolicy) + { + var found = defaultPolicy.Get(1, o => (AuditItem)null); + Assert.IsNotNull(found); + } + } + [Test] public void Get_All_Caches_Empty_List() { + var getAll = new AuditItem[] {}; + var cached = new List(); IList list = null; @@ -36,20 +84,20 @@ namespace Umbraco.Tests.Cache return cached.Any() ? new DeepCloneableList() : null; }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); using (defaultPolicy) { - var found = defaultPolicy.GetAll(new object[] {}, o => new AuditItem[] {}); + var found = defaultPolicy.GetAll(new object[] {}, o => getAll); } Assert.AreEqual(1, cached.Count); Assert.IsNotNull(list); //Do it again, ensure that its coming from the cache! - defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); + defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); using (defaultPolicy) { - var found = defaultPolicy.GetAll(new object[] { }, o => new AuditItem[] { }); + var found = defaultPolicy.GetAll(new object[] { }, o => getAll); } Assert.AreEqual(1, cached.Count); @@ -59,6 +107,12 @@ namespace Umbraco.Tests.Cache [Test] public void Get_All_Caches_As_Single_List() { + var getAll = new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }; + var cached = new List(); IList list = null; @@ -73,14 +127,10 @@ namespace Umbraco.Tests.Cache }); cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(new AuditItem[] { }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); using (defaultPolicy) { - var found = defaultPolicy.GetAll(new object[] { }, o => new[] - { - new AuditItem(1, "blah", AuditType.Copy, 123), - new AuditItem(2, "blah2", AuditType.Copy, 123) - }); + var found = defaultPolicy.GetAll(new object[] { }, o => getAll); } Assert.AreEqual(1, cached.Count); @@ -89,7 +139,9 @@ namespace Umbraco.Tests.Cache [Test] public void Get_All_Without_Ids_From_Cache() - { + { + var getAll = new[] { (AuditItem)null }; + var cache = new Mock(); cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => new DeepCloneableList @@ -98,12 +150,88 @@ namespace Umbraco.Tests.Cache new AuditItem(2, "blah2", AuditType.Copy, 123) }); - var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id); + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); using (defaultPolicy) { - var found = defaultPolicy.GetAll(new object[] { }, o => new[] { (AuditItem)null }); + var found = defaultPolicy.GetAll(new object[] { }, o => getAll); Assert.AreEqual(2, found.Length); } } + + [Test] + public void If_CreateOrUpdate_Throws_Cache_Is_Removed() + { + var getAll = new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }; + + var cacheCleared = false; + var cache = new Mock(); + cache.Setup(x => x.ClearCacheItem(It.IsAny())) + .Callback(() => + { + cacheCleared = true; + }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); + try + { + using (defaultPolicy) + { + defaultPolicy.CreateOrUpdate(new AuditItem(1, "blah", AuditType.Copy, 123), item => + { + throw new Exception("blah!"); + }); + } + } + catch + { + //we need this catch or nunit throw up + } + finally + { + Assert.IsTrue(cacheCleared); + } + } + + [Test] + public void If_Removes_Throws_Cache_Is_Removed() + { + var getAll = new[] + { + new AuditItem(1, "blah", AuditType.Copy, 123), + new AuditItem(2, "blah2", AuditType.Copy, 123) + }; + + var cacheCleared = false; + var cache = new Mock(); + cache.Setup(x => x.ClearCacheItem(It.IsAny())) + .Callback(() => + { + cacheCleared = true; + }); + + var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); + try + { + using (defaultPolicy) + { + defaultPolicy.Remove(new AuditItem(1, "blah", AuditType.Copy, 123), item => + { + throw new Exception("blah!"); + }); + } + } + catch + { + //we need this catch or nunit throw up + } + finally + { + Assert.IsTrue(cacheCleared); + } + } } } \ No newline at end of file From 1db635f24c43a78f4c359de3288b423cbc2c37db Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 2 Feb 2016 01:32:36 +0100 Subject: [PATCH 097/406] Updates DeepCloneableList to support behaviors, for the FullDataSetCachePolicy we only want to clone when writing to cache, not when reading, the cloning will then be done on individual items after filtering by the FullDataSetRepositoryCachePolicy --- .../Cache/FullDataSetRepositoryCachePolicy.cs | 33 +++++---- .../Collections/DeepCloneableList.cs | 71 ++++++++++++++----- .../Collections/ListCloneBehavior.cs | 20 ++++++ src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../DeepCloneRuntimeCacheProviderTests.cs | 2 +- .../Cache/FullDataSetCachePolicyTests.cs | 4 +- .../Collections/DeepCloneableListTests.cs | 40 ++++++++++- 7 files changed, 134 insertions(+), 37 deletions(-) create mode 100644 src/Umbraco.Core/Collections/ListCloneBehavior.cs diff --git a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs index eeb651dc09..cae7bc16e6 100644 --- a/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs +++ b/src/Umbraco.Core/Cache/FullDataSetRepositoryCachePolicy.cs @@ -85,29 +85,35 @@ namespace Umbraco.Core.Cache public override TEntity Get(TId id, Func getFromRepo) { //Force get all with cache - var found = GetAll(new TId[] { }, ids => _getAllFromRepo()); + var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull()); //we don't have anything in cache (this should never happen), just return from the repo - return found == null - ? getFromRepo(id) - : found.FirstOrDefault(x => _getEntityId(x).Equals(id)); + if (found == null) return getFromRepo(id); + var entity = found.FirstOrDefault(x => _getEntityId(x).Equals(id)); + if (entity == null) return null; + + //We must ensure to deep clone each one out manually since the deep clone list only clones one way + return (TEntity)entity.DeepClone(); } public override TEntity Get(TId id) { //Force get all with cache - var found = GetAll(new TId[] { }, ids => _getAllFromRepo()); + var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull()); //we don't have anything in cache (this should never happen), just return null - return found == null - ? null - : found.FirstOrDefault(x => _getEntityId(x).Equals(id)); + if (found == null) return null; + var entity = found.FirstOrDefault(x => _getEntityId(x).Equals(id)); + if (entity == null) return null; + + //We must ensure to deep clone each one out manually since the deep clone list only clones one way + return (TEntity)entity.DeepClone(); } public override bool Exists(TId id, Func getFromRepo) { //Force get all with cache - var found = GetAll(new TId[] {}, ids => _getAllFromRepo()); + var found = GetAll(new TId[] {}, ids => _getAllFromRepo().WhereNotNull()); //we don't have anything in cache (this should never happen), just return from the repo return found == null @@ -123,12 +129,15 @@ namespace Umbraco.Core.Cache //now that the base result has been calculated, they will all be cached. // Now we can just filter by ids if they have been supplied - return ids.Any() + return (ids.Any() ? result.Where(x => ids.Contains(_getEntityId(x))).ToArray() - : result; + : result) + //We must ensure to deep clone each one out manually since the deep clone list only clones one way + .Select(x => (TEntity)x.DeepClone()) + .ToArray(); } - protected TEntity[] PerformGetAll(Func> getFromRepo) + private TEntity[] PerformGetAll(Func> getFromRepo) { var allEntities = GetAllFromCache(); if (allEntities.Any()) diff --git a/src/Umbraco.Core/Collections/DeepCloneableList.cs b/src/Umbraco.Core/Collections/DeepCloneableList.cs index 365bf53b06..5067562aa7 100644 --- a/src/Umbraco.Core/Collections/DeepCloneableList.cs +++ b/src/Umbraco.Core/Collections/DeepCloneableList.cs @@ -14,18 +14,24 @@ namespace Umbraco.Core.Collections /// internal class DeepCloneableList : List, IDeepCloneable, IRememberBeingDirty { - /// - /// Initializes a new instance of the class that is empty and has the default initial capacity. - /// - public DeepCloneableList() + private readonly ListCloneBehavior _listCloneBehavior; + + public DeepCloneableList(ListCloneBehavior listCloneBehavior) { + _listCloneBehavior = listCloneBehavior; + } + + public DeepCloneableList(IEnumerable collection, ListCloneBehavior listCloneBehavior) : base(collection) + { + _listCloneBehavior = listCloneBehavior; } /// - /// Initializes a new instance of the class that contains elements copied from the specified collection and has sufficient capacity to accommodate the number of elements copied. + /// Default behavior is CloneOnce /// - /// The collection whose elements are copied to the new list. is null. - public DeepCloneableList(IEnumerable collection) : base(collection) + /// + public DeepCloneableList(IEnumerable collection) + : this(collection, ListCloneBehavior.CloneOnce) { } @@ -35,20 +41,47 @@ namespace Umbraco.Core.Collections /// public object DeepClone() { - var newList = new DeepCloneableList(); - foreach (var item in this) + switch (_listCloneBehavior) { - var dc = item as IDeepCloneable; - if (dc != null) - { - newList.Add((T) dc.DeepClone()); - } - else - { - newList.Add(item); - } + case ListCloneBehavior.CloneOnce: + //we are cloning once, so create a new list in none mode + // and deep clone all items into it + var newList = new DeepCloneableList(ListCloneBehavior.None); + foreach (var item in this) + { + var dc = item as IDeepCloneable; + if (dc != null) + { + newList.Add((T)dc.DeepClone()); + } + else + { + newList.Add(item); + } + } + return newList; + case ListCloneBehavior.None: + //we are in none mode, so just return a new list with the same items + return new DeepCloneableList(this, ListCloneBehavior.None); + case ListCloneBehavior.Always: + //always clone to new list + var newList2 = new DeepCloneableList(ListCloneBehavior.Always); + foreach (var item in this) + { + var dc = item as IDeepCloneable; + if (dc != null) + { + newList2.Add((T)dc.DeepClone()); + } + else + { + newList2.Add(item); + } + } + return newList2; + default: + throw new ArgumentOutOfRangeException(); } - return newList; } public bool IsDirty() diff --git a/src/Umbraco.Core/Collections/ListCloneBehavior.cs b/src/Umbraco.Core/Collections/ListCloneBehavior.cs new file mode 100644 index 0000000000..4fe935f7ff --- /dev/null +++ b/src/Umbraco.Core/Collections/ListCloneBehavior.cs @@ -0,0 +1,20 @@ +namespace Umbraco.Core.Collections +{ + internal enum ListCloneBehavior + { + /// + /// When set, DeepClone will clone the items one time and the result list behavior will be None + /// + CloneOnce, + + /// + /// When set, DeepClone will not clone any items + /// + None, + + /// + /// When set, DeepClone will always clone all items + /// + Always + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e0991e9ba7..49eaa8a629 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -188,6 +188,7 @@ + diff --git a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs index 63225f6725..39e5dd2cb1 100644 --- a/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs +++ b/src/Umbraco.Tests/Cache/DeepCloneRuntimeCacheProviderTests.cs @@ -41,7 +41,7 @@ namespace Umbraco.Tests.Cache [Test] public void Clones_List() { - var original = new DeepCloneableList(); + var original = new DeepCloneableList(ListCloneBehavior.Always); original.Add(new DeepCloneableListTests.TestClone()); original.Add(new DeepCloneableListTests.TestClone()); original.Add(new DeepCloneableListTests.TestClone()); diff --git a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs index d3df319ac7..96e22e3aff 100644 --- a/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs +++ b/src/Umbraco.Tests/Cache/FullDataSetCachePolicyTests.cs @@ -81,7 +81,7 @@ namespace Umbraco.Tests.Cache cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => { //return null if this is the first pass - return cached.Any() ? new DeepCloneableList() : null; + return cached.Any() ? new DeepCloneableList(ListCloneBehavior.CloneOnce) : null; }); var defaultPolicy = new FullDataSetRepositoryCachePolicy(cache.Object, item => item.Id, () => getAll, false); @@ -144,7 +144,7 @@ namespace Umbraco.Tests.Cache var cache = new Mock(); - cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => new DeepCloneableList + cache.Setup(x => x.GetCacheItem(It.IsAny())).Returns(() => new DeepCloneableList(ListCloneBehavior.CloneOnce) { new AuditItem(1, "blah", AuditType.Copy, 123), new AuditItem(2, "blah2", AuditType.Copy, 123) diff --git a/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs b/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs index fcc50df60c..d478192e02 100644 --- a/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs +++ b/src/Umbraco.Tests/Collections/DeepCloneableListTests.cs @@ -12,10 +12,44 @@ namespace Umbraco.Tests.Collections [TestFixture] public class DeepCloneableListTests { + [Test] + public void Deep_Clones_Each_Item_Once() + { + var list = new DeepCloneableList(ListCloneBehavior.CloneOnce); + list.Add(new TestClone()); + list.Add(new TestClone()); + list.Add(new TestClone()); + + var cloned = list.DeepClone() as DeepCloneableList; + + //Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) + Assert.IsTrue(list.SequenceEqual(cloned)); + + //Test that each instance in the list is not the same one + foreach (var item in list) + { + var clone = cloned.Single(x => x.Id == item.Id); + Assert.AreNotSame(item, clone); + } + + //clone again from the clone - since it's clone once the items should be the same + var cloned2 = cloned.DeepClone() as DeepCloneableList; + + //Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID) + Assert.IsTrue(cloned.SequenceEqual(cloned2)); + + //Test that each instance in the list is the same one + foreach (var item in cloned) + { + var clone = cloned2.Single(x => x.Id == item.Id); + Assert.AreSame(item, clone); + } + } + [Test] public void Deep_Clones_All_Elements() { - var list = new DeepCloneableList(); + var list = new DeepCloneableList(ListCloneBehavior.Always); list.Add(new TestClone()); list.Add(new TestClone()); list.Add(new TestClone()); @@ -30,7 +64,7 @@ namespace Umbraco.Tests.Collections [Test] public void Clones_Each_Item() { - var list = new DeepCloneableList(); + var list = new DeepCloneableList(ListCloneBehavior.Always); list.Add(new TestClone()); list.Add(new TestClone()); list.Add(new TestClone()); @@ -46,7 +80,7 @@ namespace Umbraco.Tests.Collections [Test] public void Cloned_Sequence_Equals() { - var list = new DeepCloneableList(); + var list = new DeepCloneableList(ListCloneBehavior.Always); list.Add(new TestClone()); list.Add(new TestClone()); list.Add(new TestClone()); From c659e1ff8f309153ce2620613a98d0ebb2192112 Mon Sep 17 00:00:00 2001 From: Rune Strand Date: Tue, 2 Feb 2016 10:15:57 +0100 Subject: [PATCH 098/406] Adds button states for hover, focus and active --- src/Umbraco.Web.UI.Client/src/less/mixins.less | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/mixins.less b/src/Umbraco.Web.UI.Client/src/less/mixins.less index 38df48a457..8e986b5b00 100644 --- a/src/Umbraco.Web.UI.Client/src/less/mixins.less +++ b/src/Umbraco.Web.UI.Client/src/less/mixins.less @@ -161,7 +161,7 @@ // Mixin for form field states -//SD: I've had to modify this slightly to work nicely with angular validation , note the +//SD: I've had to modify this slightly to work nicely with angular validation , note the // additional targetting of the ng-invalid class. .formFieldState(@textColor: #555, @borderColor: #ccc, @backgroundColor: #f5f5f5) { // Set the text color @@ -522,10 +522,14 @@ *background-color: @endColor; /* Darken IE7 buttons by default so they stand out more given they won't have borders */ .reset-filter(); + // button states + &:hover, &:focus, &:active { + background-color: darken(@startColor, 2%); + } + // in these cases the gradient won't cover the background, so we override &:hover, &:focus, &:active, &.active, &.disabled, &[disabled] { color: @textColor; - background-color: @endColor; *background-color: darken(@endColor, 5%); } From 0af97f63e27a02d5f577409594dcd11a9a5e8253 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 2 Feb 2016 11:11:47 +0100 Subject: [PATCH 099/406] U4-7857 Flexible Load Balancing does not sync with the correct timeout threshold --- src/Umbraco.Core/Sync/DatabaseServerMessenger.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs index 8a4d79725a..87fc694fd1 100644 --- a/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs +++ b/src/Umbraco.Core/Sync/DatabaseServerMessenger.cs @@ -198,7 +198,7 @@ namespace Umbraco.Core.Sync if (_released) return; - if ((DateTime.UtcNow - _lastSync).Seconds <= _options.ThrottleSeconds) + if ((DateTime.UtcNow - _lastSync).TotalSeconds <= _options.ThrottleSeconds) return; _syncing = true; From ae26a19d4a0640d1184ae242fe7f97ee7b748f5f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 2 Feb 2016 11:41:52 +0100 Subject: [PATCH 100/406] Fixes: U4-7675 Changing doc type icon doesn't set dirty property for form --- .../components/editor/umbeditorheader.directive.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index bf87d764a6..27a1ec4e6d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -15,6 +15,10 @@ } else { scope.icon = model.icon; } + + // set form to dirty + ctrl.$setDirty(); + scope.dialogModel.show = false; scope.dialogModel = null; } @@ -23,6 +27,7 @@ } var directive = { + require: '^form', transclude: true, restrict: 'E', replace: true, From 4360d49531aa25c078f5dea060e9d66a4158769a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 2 Feb 2016 11:42:36 +0100 Subject: [PATCH 101/406] fix file indention --- .../editor/umbeditorheader.directive.js | 92 +++++++++---------- 1 file changed, 46 insertions(+), 46 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index 27a1ec4e6d..e425bde042 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -1,57 +1,57 @@ (function() { - 'use strict'; + 'use strict'; - function EditorHeaderDirective(iconHelper) { + function EditorHeaderDirective(iconHelper) { - function link(scope, el, attr, ctrl) { + function link(scope, el, attr, ctrl) { - scope.openIconPicker = function() { - scope.dialogModel = { - view: "iconpicker", - show: true, - submit: function(model) { - if (model.color) { - scope.icon = model.icon + " " + model.color; - } else { - scope.icon = model.icon; + scope.openIconPicker = function() { + scope.dialogModel = { + view: "iconpicker", + show: true, + submit: function(model) { + if (model.color) { + scope.icon = model.icon + " " + model.color; + } else { + scope.icon = model.icon; + } + + // set form to dirty + ctrl.$setDirty(); + + scope.dialogModel.show = false; + scope.dialogModel = null; } - - // set form to dirty - ctrl.$setDirty(); - - scope.dialogModel.show = false; - scope.dialogModel = null; - } + }; }; - }; - } + } - var directive = { - require: '^form', - transclude: true, - restrict: 'E', - replace: true, - templateUrl: 'views/components/editor/umb-editor-header.html', - scope: { - tabs: "=", - actions: "=", - name: "=", - nameLocked: "=", - menu: "=", - icon: "=", - hideIcon: "@", - alias: "=", - hideAlias: "@", - description: "=", - hideDescription: "@", - navigation: "=" - }, - link: link - }; + var directive = { + require: '^form', + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/components/editor/umb-editor-header.html', + scope: { + tabs: "=", + actions: "=", + name: "=", + nameLocked: "=", + menu: "=", + icon: "=", + hideIcon: "@", + alias: "=", + hideAlias: "@", + description: "=", + hideDescription: "@", + navigation: "=" + }, + link: link + }; - return directive; - } + return directive; + } - angular.module('umbraco.directives').directive('umbEditorHeader', EditorHeaderDirective); + angular.module('umbraco.directives').directive('umbEditorHeader', EditorHeaderDirective); })(); From bd2fc71dc589498fd77b4b46df50c74fa8411fc2 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 2 Feb 2016 11:55:24 +0100 Subject: [PATCH 102/406] U4-7276 When creating folders in the "Partials" view folder a YSOD appears (Umbraco 7.3 and 7.4) --- .../umbraco/create/PartialViewTasksBase.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs index 042bf312d1..abb1299507 100644 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs +++ b/src/Umbraco.Web/umbraco.presentation/umbraco/create/PartialViewTasksBase.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Web; using Umbraco.Core.CodeAnnotations; using Umbraco.Core.Configuration; using Umbraco.Core.IO; @@ -52,7 +53,7 @@ namespace umbraco if (IsPartialViewMacro == false) { var attempt = fileService.CreatePartialView(model, snippetName, User.Id); - _returnUrl = string.Format("settings/views/EditView.aspx?treeType=partialViews&file={0}", model.Path.TrimStart('/')); + _returnUrl = string.Format("settings/views/EditView.aspx?treeType=partialViews&file={0}", HttpUtility.UrlEncode(model.Path.TrimStart('/').Replace("\\", "/"))); return attempt.Success; } else @@ -68,7 +69,7 @@ namespace umbraco macroService.Save(new Macro(attempt.Result.Alias, attempt.Result.Alias) { ScriptPath = virtualPath }); } - _returnUrl = string.Format("settings/views/EditView.aspx?treeType=partialViewMacros&file={0}", model.Path.TrimStart('/')); + _returnUrl = string.Format("settings/views/EditView.aspx?treeType=partialViewMacros&file={0}", HttpUtility.UrlEncode(model.Path.TrimStart('/').Replace("\\", "/"))); return attempt.Success; } From 6583ff443973388bc2030769ab26fda550533889 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 2 Feb 2016 12:12:51 +0100 Subject: [PATCH 103/406] U4-7821 KeepUserLoggedIn with a long umbracoTimeOutInMinutes has logout issues --- .../Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs b/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs index 2b2ecb4295..188a235c8c 100644 --- a/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs +++ b/src/Umbraco.Web/Security/Identity/UmbracoBackOfficeCookieAuthOptions.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web.Security.Identity CookieName = securitySection.AuthCookieName; CookieHttpOnly = true; CookieSecure = forceSsl ? CookieSecureOption.Always : CookieSecureOption.SameAsRequest; - CookiePath = "/"; + CookiePath = "/"; //Custom cookie manager so we can filter requests CookieManager = new BackOfficeCookieManager(new SingletonUmbracoContextAccessor(), explicitPaths); @@ -84,7 +84,7 @@ namespace Umbraco.Web.Security.Identity if (ticket.Properties.IsPersistent) { - cookieOptions.Expires = expiresUtc.ToUniversalTime().DateTime; + cookieOptions.Expires = expiresUtc.UtcDateTime; } return cookieOptions; From e9af8bfee1fb49fa9c966589eca63e9a562d647e Mon Sep 17 00:00:00 2001 From: Rune Strand Date: Tue, 2 Feb 2016 13:37:52 +0100 Subject: [PATCH 104/406] click in grid RTE format dropdown no longer looses focus from the editor --- .../directives/components/events/events.directive.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js index 066f91be53..1b9dc090bb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/events/events.directive.js @@ -150,6 +150,7 @@ angular.module('umbraco.directives') function oneTimeClick(event) { var el = event.target.nodeName; + //ignore link and button clicks var els = ["INPUT","A","BUTTON"]; if(els.indexOf(el) >= 0){return;} @@ -167,6 +168,12 @@ angular.module('umbraco.directives') return; } + // ignore clicks in tinyMCE dropdown(floatpanel) + var floatpanel = $(el).parents(".mce-floatpanel"); + if (floatpanel.length === 1) { + return; + } + //ignore clicks inside this element if( $(element).has( $(event.target) ).length > 0 ){ return; From 8791f7ee970c20df5a41d00980107ae4d1cb8ac5 Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 2 Feb 2016 13:52:55 +0100 Subject: [PATCH 105/406] U4-7713 - fix CountPublished --- src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 1f5cc1ecd4..7d3557a81d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -689,8 +689,7 @@ namespace Umbraco.Core.Persistence.Repositories public int CountPublished() { var sql = GetBaseQuery(true).Where(x => x.Trashed == false) - .Where(x => x.Published == true) - .Where(x => x.Newest == true); + .Where(x => x.Published == true); return Database.ExecuteScalar(sql); } From bb52467f904035cb73151431768e0d9d701360c4 Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Tue, 2 Feb 2016 14:08:12 +0100 Subject: [PATCH 106/406] Fixes: U4-7703 TinyMCE - can't add additional css via custom config (v7.3.4) http://issues.umbraco.org/issue/U4-7703 --- .../src/common/directives/components/grid/grid.rte.directive.js | 2 +- .../src/views/propertyeditors/rte/rte.controller.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js index 0fccd4eabb..eab90358c9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/grid/grid.rte.directive.js @@ -104,7 +104,7 @@ angular.module("umbraco.directives") statusbar: false, relative_urls: false, toolbar: toolbar, - content_css: stylesheets.join(','), + content_css: stylesheets, style_formats: styleFormats, autoresize_bottom_margin: 0 }; diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js index 928c504937..0e1aff730f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.controller.js @@ -117,7 +117,7 @@ angular.module("umbraco") width: editorConfig.dimensions.width, maxImageSize: editorConfig.maxImageSize, toolbar: toolbar, - content_css: stylesheets.join(','), + content_css: stylesheets, relative_urls: false, style_formats: styleFormats }; From 137de20cef33e092a73e0d05c9f00faf89650028 Mon Sep 17 00:00:00 2001 From: Rune Strand Date: Tue, 2 Feb 2016 14:30:57 +0100 Subject: [PATCH 107/406] Remove gradient from btnDanger --- src/Umbraco.Web.UI.Client/src/less/variables.less | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 970317fca9..5bddbd8022 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -119,7 +119,7 @@ @btnWarningBackgroundHighlight: @orange; @btnDangerBackground: #ee5f5b; -@btnDangerBackgroundHighlight: #bd362f; +@btnDangerBackgroundHighlight: #ee5f5b; @btnInverseBackground: #444; @btnInverseBackgroundHighlight: @grayDarker; From 4812f1d603f5aa1f01f0b96923a497bdf0c2abf3 Mon Sep 17 00:00:00 2001 From: Simon Busborg Date: Tue, 2 Feb 2016 15:11:50 +0100 Subject: [PATCH 108/406] Fixes: U4-7725 Backend / Full witdth image / Settings / Set a background image - Is out of focus. --- .../src/less/components/overlays.less | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index 105b107cf1..0af0555b89 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -189,7 +189,9 @@ /*ensures dialogs doesnt have side-by-side labels*/ .umb-overlay .control-label, -.umb-overlay .form-horizontal .control-label { +.umb-overlay .form-horizontal .control-label, +.form-horizontal .umb-overlay .control-label + { width: 100%; display: block; box-sizing: border-box; @@ -198,6 +200,8 @@ } -.umb-overlay .controls-row { +.umb-overlay .controls-row, +.umb-overlay .form-horizontal .controls, +.form-horizontal .umb-overlay .controls { margin-left: 0 !important; } From 98b8aedc4d1f57ccb51637f9c7e20a8402f28923 Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 2 Feb 2016 15:13:57 +0100 Subject: [PATCH 109/406] U4-7849 Unclear error message when applying public access --- src/Umbraco.Web.UI.Client/src/less/hacks.less | 93 +++++++++++- .../umbraco/dialogs/protectPage.aspx | 105 +++++++------- .../umbraco/dialogs/protectPage.aspx.cs | 134 +++++++++--------- 3 files changed, 218 insertions(+), 114 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/hacks.less b/src/Umbraco.Web.UI.Client/src/less/hacks.less index 04da8fb0af..c0e80b4e48 100644 --- a/src/Umbraco.Web.UI.Client/src/less/hacks.less +++ b/src/Umbraco.Web.UI.Client/src/less/hacks.less @@ -67,4 +67,95 @@ iframe, .content-column-body { } .icon-chevron-down:before { content: "\e0c9"; -} \ No newline at end of file +} + + +/* Styling for validation in Public Access */ + +.pa-umb-overlay { + -webkit-font-smoothing: antialiased; + font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.pa-umb-overlay + .pa-umb-overlay { + padding-top: 30px; + border-top: 1px solid @grayLight; +} + +.pa-select-type { + display: flex; + flex-wrap: nowrap; + flex-direction: row; + justify-content: center; + align-items: flex-start; + + margin-top: 15px; +} + +.pa-select-type label { + padding: 0 20px; +} + +.pa-access-header { + font-weight: bold; + margin: 0 0 3px 0; + padding-bottom: 0; +} + +.pa-access-description { + color: #b3b3b3; + margin: 0; +} + +.pa-validation-message { + padding: 6px 12px !important; + margin: 5px 0 0 0 !important; + display: inline-block; +} + +.pa-select-pages label { + margin: 0; + font-size: 15px; +} + +.pa-select-pages label + .controls-row { + padding-top: 0; +} + +.pa-select-pages .umb-detail { + font-size: 13px; + margin: 2px 0 5px; +} + +.pa-choose-page a { + color: @blue; + font-size: 15px; +} + +.pa-choose-page a:hover, .pa-choose-page a:active, .pa-choose-page a:focus { + color: @blueDark; + text-decoration: none; +} + +.pa-choose-page a:before { + content:"+"; + margin-right: 3px; + font-weight: bold; +} + +.pa-choose-page .treePickerTitle { + font-weight: bold; + font-size: 13px; + font-style: italic; + background: whitesmoke; + padding: 3px 5px; + color: grey; + + border-bottom: none; +} + + +.pa-form + .pa-form { + margin-top: 10px; +} diff --git a/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx b/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx index 0a424f506f..be515f693c 100644 --- a/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx +++ b/src/Umbraco.Web.UI/umbraco/dialogs/protectPage.aspx @@ -5,30 +5,30 @@