From 6c172702be06770566bb1271003f40903a2c7e11 Mon Sep 17 00:00:00 2001 From: Kevin Giszewski Date: Wed, 27 Sep 2017 11:32:36 -0400 Subject: [PATCH 01/20] Fix for U4-6309 --- .../src/views/propertyeditors/rte/rte.controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d17310d65a..6e70bbf3f7 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 @@ -257,7 +257,7 @@ angular.module("umbraco") editor.on('ObjectResized', function (e) { - var qs = "?width=" + e.width + "&height=" + e.height; + var qs = "?width=" + e.width + "&height=" + e.height + "&mode=max"; var srcAttr = $(e.target).attr("src"); var path = srcAttr.split("?")[0]; $(e.target).attr("data-mce-src", path + qs); From a1cd22208ba544166736e241e21a196ab95738d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristoffer=20Tver=C3=A5?= Date: Fri, 12 Jan 2018 10:45:59 +0100 Subject: [PATCH 02/20] Extra check before assigning value to currentColor In version 7.7.7 with the latest version of LeBlender (1.0.9.2), colorpickers will throw a javascript error in console. Applying this small check will resolve that issue. ref: https://our.umbraco.org/forum/contributing-to-umbraco-cms/88890-color-picker-cannot-read-property-hasownproperty-of-null --- .../colorpicker/colorpicker.controller.js | 228 +++++++++--------- 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js index 7d847dbc83..5f95120d46 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/colorpicker/colorpicker.controller.js @@ -1,46 +1,46 @@ -function ColorPickerController($scope) { - - $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; - - if ($scope.isConfigured) { - - for (var key in $scope.model.config.items) { - if (!$scope.model.config.items[key].hasOwnProperty("value")) - $scope.model.config.items[key] = { value: $scope.model.config.items[key], label: $scope.model.config.items[key] }; - } - - $scope.model.useLabel = isTrue($scope.model.config.useLabel); - initActiveColor(); - } +function ColorPickerController($scope) { + + $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + + if ($scope.isConfigured) { + + for (var key in $scope.model.config.items) { + if (!$scope.model.config.items[key].hasOwnProperty("value")) + $scope.model.config.items[key] = { value: $scope.model.config.items[key], label: $scope.model.config.items[key] }; + } + + $scope.model.useLabel = isTrue($scope.model.config.useLabel); + initActiveColor(); + } + + $scope.toggleItem = function (color) { + + var currentColor = ($scope.model.value && $scope.model.value.hasOwnProperty("value")) + ? $scope.model.value.value + : $scope.model.value; - $scope.toggleItem = function (color) { - - var currentColor = $scope.model.value.hasOwnProperty("value") - ? $scope.model.value.value - : $scope.model.value; - var newColor; - if (currentColor === color.value) { - // deselect - $scope.model.value = $scope.model.useLabel ? { value: "", label: "" } : ""; - newColor = ""; - } - else { + if (currentColor === color.value) { + // deselect + $scope.model.value = $scope.model.useLabel ? { value: "", label: "" } : ""; + newColor = ""; + } + else { // select - $scope.model.value = $scope.model.useLabel ? { value: color.value, label: color.label } : color.value; + $scope.model.value = $scope.model.useLabel ? { value: color.value, label: color.label } : color.value; newColor = color.value; - } - - // this is required to re-validate - $scope.propertyForm.modelValue.$setViewValue(newColor); - }; + } + + // this is required to re-validate + $scope.propertyForm.modelValue.$setViewValue(newColor); + }; // Method required by the valPropertyValidator directive (returns true if the property editor has at least one color selected) - $scope.validateMandatory = function () { - var isValid = !$scope.model.validation.mandatory || ( - $scope.model.value != null - && $scope.model.value != "" - && (!$scope.model.value.hasOwnProperty("value") || $scope.model.value.value !== "") + $scope.validateMandatory = function () { + var isValid = !$scope.model.validation.mandatory || ( + $scope.model.value != null + && $scope.model.value != "" + && (!$scope.model.value.hasOwnProperty("value") || $scope.model.value.value !== "") ); return { isValid: isValid, @@ -48,83 +48,83 @@ function ColorPickerController($scope) { errorKey: "required" }; } - $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; - - // A color is active if it matches the value and label of the model. - // If the model doesn't store the label, ignore the label during the comparison. - $scope.isActiveColor = function (color) { - - // no value - if (!$scope.model.value) - return false; - - // Complex color (value and label)? - if (!$scope.model.value.hasOwnProperty("value")) - return $scope.model.value === color.value; - - return $scope.model.value.value === color.value && $scope.model.value.label === color.label; - }; - - // Finds the color best matching the model's color, - // and sets the model color to that one. This is useful when - // either the value or label was changed on the data type. - function initActiveColor() { - - // no value - if (!$scope.model.value) - return; - - // Complex color (value and label)? - if (!$scope.model.value.hasOwnProperty("value")) - return; - - var modelColor = $scope.model.value.value; - var modelLabel = $scope.model.value.label; - - // Check for a full match or partial match. - var foundItem = null; - - // Look for a fully matching color. - for (var key in $scope.model.config.items) { - var item = $scope.model.config.items[key]; - if (item.value == modelColor && item.label == modelLabel) { - foundItem = item; - break; - } - } - - // Look for a color with a matching value. - if (!foundItem) { - for (var key in $scope.model.config.items) { - var item = $scope.model.config.items[key]; - if (item.value == modelColor) { - foundItem = item; - break; - } - } - } - - // Look for a color with a matching label. - if (!foundItem) { - for (var key in $scope.model.config.items) { - var item = $scope.model.config.items[key]; - if (item.label == modelLabel) { - foundItem = item; - break; - } - } - } - - // If a match was found, set it as the active color. - if (foundItem) { - $scope.model.value.value = foundItem.value; - $scope.model.value.label = foundItem.label; - } - } - - // figures out if a value is trueish enough - function isTrue(bool) { - return !!bool && bool !== "0" && angular.lowercase(bool) !== "false"; + $scope.isConfigured = $scope.model.config && $scope.model.config.items && _.keys($scope.model.config.items).length > 0; + + // A color is active if it matches the value and label of the model. + // If the model doesn't store the label, ignore the label during the comparison. + $scope.isActiveColor = function (color) { + + // no value + if (!$scope.model.value) + return false; + + // Complex color (value and label)? + if (!$scope.model.value.hasOwnProperty("value")) + return $scope.model.value === color.value; + + return $scope.model.value.value === color.value && $scope.model.value.label === color.label; + }; + + // Finds the color best matching the model's color, + // and sets the model color to that one. This is useful when + // either the value or label was changed on the data type. + function initActiveColor() { + + // no value + if (!$scope.model.value) + return; + + // Complex color (value and label)? + if (!$scope.model.value.hasOwnProperty("value")) + return; + + var modelColor = $scope.model.value.value; + var modelLabel = $scope.model.value.label; + + // Check for a full match or partial match. + var foundItem = null; + + // Look for a fully matching color. + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.value == modelColor && item.label == modelLabel) { + foundItem = item; + break; + } + } + + // Look for a color with a matching value. + if (!foundItem) { + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.value == modelColor) { + foundItem = item; + break; + } + } + } + + // Look for a color with a matching label. + if (!foundItem) { + for (var key in $scope.model.config.items) { + var item = $scope.model.config.items[key]; + if (item.label == modelLabel) { + foundItem = item; + break; + } + } + } + + // If a match was found, set it as the active color. + if (foundItem) { + $scope.model.value.value = foundItem.value; + $scope.model.value.label = foundItem.label; + } + } + + // figures out if a value is trueish enough + function isTrue(bool) { + return !!bool && bool !== "0" && angular.lowercase(bool) !== "false"; } } From 87f09b2ee6c8edb09fa8feb4f497d1d9e15a804a Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 18 Jan 2018 10:49:18 +0000 Subject: [PATCH 03/20] U4-10845 MultiNodeTreePickerPropertyConverter - trashed content causes database lookups If the first ID in the MNTP value is a trashed content node, then the `ConvertSourceToObject` method would check if the ID is a media or member node, both of these incur a database call. This can cause a detrimental performance impact on the website. This patch uses the `startNode.type` prevalue to detect the node type, (e.g. Content, Media or Member). The prevalue is cached in a private `ConcurrentDictionary` and refreshed using the `DataTypeCacheRefresher` event. > _Following the same caching-pattern as the Slider, Tags and MediaPicker value-converters._ --- .../Cache/DataTypeCacheRefresher.cs | 1 + .../MultiNodeTreePickerPropertyConverter.cs | 113 ++++++++++++------ 2 files changed, 79 insertions(+), 35 deletions(-) diff --git a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs index 65d366e2ad..d28fe5cf88 100644 --- a/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs @@ -117,6 +117,7 @@ namespace Umbraco.Web.Cache LegacyMediaPickerPropertyConverter.ClearCaches(); SliderValueConverter.ClearCaches(); MediaPickerPropertyConverter.ClearCaches(); + MultiNodeTreePickerPropertyConverter.ClearCaches(); base.Refresh(jsonPayload); diff --git a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs index 249b7e0cac..abe444435f 100644 --- a/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs +++ b/src/Umbraco.Web/PropertyEditors/ValueConverters/MultiNodeTreePickerPropertyConverter.cs @@ -8,16 +8,18 @@ // -------------------------------------------------------------------------------------------------------------------- using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; +using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Models; using Umbraco.Core.Models.PublishedContent; using Umbraco.Core.PropertyEditors; using Umbraco.Core.PropertyEditors.ValueConverters; -using Umbraco.Web.Extensions; +using Umbraco.Core.Services; namespace Umbraco.Web.PropertyEditors.ValueConverters { @@ -32,6 +34,20 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters [PropertyValueCache(PropertyCacheValue.XPath, PropertyCacheLevel.Content)] public class MultiNodeTreePickerPropertyConverter : PropertyValueConverterBase { + private readonly IDataTypeService _dataTypeService; + + //TODO: Remove this ctor in v8 since the other one will use IoC + public MultiNodeTreePickerPropertyConverter() + : this(ApplicationContext.Current.Services.DataTypeService) + { } + + public MultiNodeTreePickerPropertyConverter(IDataTypeService dataTypeService) + : base() + { + if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); + _dataTypeService = dataTypeService; + } + /// /// The properties to exclude. /// @@ -125,6 +141,24 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters var umbHelper = new UmbracoHelper(UmbracoContext.Current); + Func contentFetcher; + + switch (GetPublishedContentType(propertyType.DataTypeId)) + { + case PublishedItemType.Media: + contentFetcher = umbHelper.TypedMember; + break; + + case PublishedItemType.Member: + contentFetcher = umbHelper.TypedMedia; + break; + + case PublishedItemType.Content: + default: + contentFetcher = umbHelper.TypedContent; + break; + } + if (propertyType.PropertyEditorAlias.Equals(Constants.PropertyEditors.MultiNodeTreePickerAlias)) { var nodeIds = (int[])source; @@ -133,14 +167,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { var multiNodeTreePicker = new List(); - var objectType = UmbracoObjectTypes.Unknown; - foreach (var nodeId in nodeIds) { - var multiNodeTreePickerItem = - GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Document, umbHelper.TypedContent) - ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Media, umbHelper.TypedMedia) - ?? GetPublishedContent(nodeId, ref objectType, UmbracoObjectTypes.Member, umbHelper.TypedMember); + var multiNodeTreePickerItem = contentFetcher(nodeId); if (multiNodeTreePickerItem != null) { @@ -163,14 +192,9 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters { var multiNodeTreePicker = new List(); - var objectType = UmbracoObjectTypes.Unknown; - foreach (var udi in udis) { - var multiNodeTreePickerItem = - GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Document, umbHelper.TypedContent) - ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Media, umbHelper.TypedMedia) - ?? GetPublishedContent(udi, ref objectType, UmbracoObjectTypes.Member, umbHelper.TypedMember); + var multiNodeTreePickerItem = contentFetcher(udi); if (multiNodeTreePickerItem != null) { multiNodeTreePicker.Add(multiNodeTreePickerItem); @@ -186,31 +210,50 @@ namespace Umbraco.Web.PropertyEditors.ValueConverters return source; } - /// - /// Attempt to get an IPublishedContent instance based on ID and content type - /// - /// The content node ID - /// The type of content being requested - /// The type of content expected/supported by - /// A function to fetch content of type - /// The requested content, or null if either it does not exist or does not match - private IPublishedContent GetPublishedContent(T nodeId, ref UmbracoObjectTypes actualType, UmbracoObjectTypes expectedType, Func contentFetcher) + private PublishedItemType GetPublishedContentType(int dataTypeId) { - // is the actual type supported by the content fetcher? - if (actualType != UmbracoObjectTypes.Unknown && actualType != expectedType) - { - // no, return null - return null; - } + // GetPreValuesCollectionByDataTypeId is cached at repository level; + // still, the collection is deep-cloned so this is kinda expensive, + // better to cache here + trigger refresh in DataTypeCacheRefresher + // e.g. https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/src/Umbraco.Web/Cache/DataTypeCacheRefresher.cs#L116-L119 - // attempt to get the content - var content = contentFetcher(nodeId); - if (content != null) + return Storages.GetOrAdd(dataTypeId, id => { - // if we found the content, assign the expected type to the actual type so we don't have to keep looking for other types of content - actualType = expectedType; - } - return content; + var preValue = _dataTypeService + .GetPreValuesCollectionByDataTypeId(id) + .PreValuesAsDictionary + .FirstOrDefault(x => string.Equals(x.Key, "startNode", StringComparison.InvariantCultureIgnoreCase)) + .Value; + + if (preValue != null && string.IsNullOrWhiteSpace(preValue.Value) == false) + { + var data = JsonConvert.DeserializeAnonymousType(preValue.Value, new { type = string.Empty }); + if (data != null) + { + switch (data.type.ToUpperInvariant()) + { + case "MEDIA": + return PublishedItemType.Media; + + case "MEMBER": + return PublishedItemType.Member; + + case "CONTENT": + default: + return PublishedItemType.Content; + } + } + } + + return PublishedItemType.Content; + }); + } + + private static readonly ConcurrentDictionary Storages = new ConcurrentDictionary(); + + internal static void ClearCaches() + { + Storages.Clear(); } } } From 60ce36a7f254921b12019645f0ddf7fffe1226a0 Mon Sep 17 00:00:00 2001 From: Ian Houghton Date: Mon, 22 Jan 2018 22:03:35 +0000 Subject: [PATCH 04/20] U4-10864 add button when search returns no results, to clear the search --- .../src/controllers/search.controller.js | 5 +++-- .../views/components/application/umb-navigation.html | 12 ++++++++---- src/Umbraco.Web.UI/umbraco/config/lang/en.xml | 1 + src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js index 57ddb6babd..ef566ac4fa 100644 --- a/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/controllers/search.controller.js @@ -14,7 +14,6 @@ function SearchController($scope, searchService, $log, $location, navigationServ $scope.isSearching = false; $scope.selectedResult = -1; - $scope.navigateResults = function (ev) { //38: up 40: down, 13: enter @@ -34,12 +33,14 @@ function SearchController($scope, searchService, $log, $location, navigationServ } }; - var group = undefined; var groupIndex = -1; var itemIndex = -1; $scope.selectedItem = undefined; + $scope.clearSearch = function () { + $scope.searchTerm = null; + }; function iterateResults(up) { //default group diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index d3eed009c3..94cc589355 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -35,10 +35,14 @@ - - + + +
+
    diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index d8235f61af..84bf7449f8 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -604,6 +604,7 @@ Permissions Search Sorry, we can not find what you are looking for + Clear search No items have been added Server Show 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 d09a904ded..5b0c7f8507 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -604,6 +604,7 @@ Permissions Search Sorry, we can not find what you are looking for + Clear search No items have been added Server Show From ca8638526938a734fe226bdf184cd40da8483b7e Mon Sep 17 00:00:00 2001 From: Pete Duncanson Date: Tue, 23 Jan 2018 15:10:09 +0000 Subject: [PATCH 05/20] Prevent Pinterest messing with media in the back office Fixes: http://issues.umbraco.org/issue/U4-10865 --- src/Umbraco.Web.UI/umbraco/Views/Default.cshtml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml index f8b7eb86cf..677d28d362 100644 --- a/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml +++ b/src/Umbraco.Web.UI/umbraco/Views/Default.cshtml @@ -43,6 +43,7 @@ + Umbraco From c022397d863f603ee4afae344bc852fbc66db63e Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 23 Jan 2018 20:54:47 +0100 Subject: [PATCH 06/20] U4-10846 - fix member group cache --- .../Repositories/MemberGroupRepository.cs | 25 +++++++++---------- .../Cache/MemberGroupCacheRefresher.cs | 7 +++--- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index c7e8a5c328..a477b6fd40 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -131,18 +131,17 @@ namespace Umbraco.Core.Persistence.Repositories public IMemberGroup GetByName(string name) { - return IsolatedCache.GetCacheItem( - string.Format("{0}.{1}", typeof (IMemberGroup).FullName, name), - () => - { - var qry = new Query().Where(group => group.Name.Equals(name)); - var result = GetByQuery(qry); - return result.FirstOrDefault(); - }, - //cache for 5 mins since that is the default in the RuntimeCacheProvider - TimeSpan.FromMinutes(5), - //sliding is true - true); + // cannot use name in the cache key, 'cos when clearing the cache for a given + // member group that has been renamed, we clear the new name but not the old + // name - it just cannot work - and we cannot use the dirty props 'cos it won't + // work on distributed cache - and besides, we end up caching both by name and + // by id + // disabling the cache entirely for now - if we want to cache we'd need to + // implement some sort of cross-reference name/id cache + + var qry = new Query().Where(group => group.Name.Equals(name)); + var result = GetByQuery(qry); + return result.FirstOrDefault(); } public IMemberGroup CreateIfNotExists(string roleName) @@ -337,4 +336,4 @@ namespace Umbraco.Core.Persistence.Repositories /// internal static event TypedEventHandler> SavedMemberGroup; } -} \ No newline at end of file +} diff --git a/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs index 2d61b254a0..a27dca47ca 100644 --- a/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs @@ -108,11 +108,10 @@ namespace Umbraco.Web.Cache { if (payload != null && memberGroupCache) { - memberGroupCache.Result.ClearCacheByKeySearch(string.Format("{0}.{1}", typeof(IMemberGroup).FullName, payload.Name)); memberGroupCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(payload.Id)); - } + } }); - + } } -} \ No newline at end of file +} From ee75158dd05f7f87c466ce6e6dee4b34c57fdfc0 Mon Sep 17 00:00:00 2001 From: Anders Bjerner Date: Tue, 23 Jan 2018 21:55:15 +0100 Subject: [PATCH 07/20] Nested Content should use the new turquoise/purple colors as the rest of the backoffice --- .../src/less/components/umb-nested-content.less | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 4f24fcf504..3314195c0d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -31,13 +31,13 @@ .nested-content__item--active:not(.nested-content__item--single) { - background: #f8f8f8; + background: @gray-10; } .nested-content__item.ui-sortable-placeholder { - background: #f8f8f8; - border: 1px dashed #d9d9d9; + background: @gray-10; + border-bottom: 1px dashed @purple-l3; visibility: visible !important; height: 55px; margin-top: -1px; @@ -56,7 +56,7 @@ .nested-content__header-bar { padding: 15px 20px; - border-bottom: 1px dashed #e0e0e0; + border-bottom: 1px dashed @purple-l3; text-align: right; cursor: pointer; background-color: white; @@ -113,8 +113,8 @@ .nested-content__icon--active { color: white; - background: #2e8aea; - border-color: #2e8aea; + background: @turquoise-d1; + border-color: @turquoise-d1; text-decoration: none; } @@ -146,7 +146,7 @@ .nested-content__content { - border-bottom: 1px dashed #e0e0e0; + border-bottom: 1px dashed @purple-l3; } .nested-content__content .umb-control-group { @@ -165,7 +165,7 @@ clear: both; font-size: 14px; color: #555; - background: #f8f8f8; + background: @gray-10; border-radius: 15px; } From bb9e23ca3ea080d2a4c0191747fff38b3c4ca683 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 18 Jan 2018 11:40:46 +0000 Subject: [PATCH 08/20] Proposed patch for U4-10756 - Guid TypedContent performance This is a proposal for a temporary workaround to issue U4-10756. It uses a local static ConcurrentDictionary to store a lookup of Guid/int values. If the Guid isn't in the lookup, then the traditional XPath is used, which would add the resulting node ID (int) to the lookup. If the lookup contains the Guid, then the returned int value will be used to perform a more efficient retrieval. --- src/Umbraco.Web/PublishedContentQuery.cs | 37 ++++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index bfe8fb5260..a609a609b7 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Xml.XPath; @@ -224,14 +225,38 @@ namespace Umbraco.Web { var doc = cache.GetById(id); return doc; - } + } + + private static ConcurrentDictionary _guidToIntLoopkup; private IPublishedContent TypedDocumentById(Guid id, ContextualPublishedCache cache) - { - // todo: in v8, implement in a more efficient way - var legacyXml = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; - var xpath = legacyXml ? "//node [@key=$guid]" : "//* [@key=$guid]"; - var doc = cache.GetSingleByXPath(xpath, new XPathVariable("guid", id.ToString())); + { + // We create the lookup when we first need it + if (_guidToIntLoopkup == null) + _guidToIntLoopkup = new ConcurrentDictionary(); + + IPublishedContent doc; + + // Check if the lookup contains the GUID/INT value + if (_guidToIntLoopkup.TryGetValue(id, out int nodeId) == false) + { + // If not, then we perform an inefficient XPath for the GUID + + // todo: in v8, implement in a more efficient way + var legacyXml = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; + var xpath = legacyXml ? "//node[@key=$guid]" : "//*[@key=$guid]"; + doc = cache.GetSingleByXPath(xpath, new XPathVariable("guid", id.ToString())); + + // When we have the node, we add the GUID/INT value to the lookup + if (doc != null) + _guidToIntLoopkup.TryAdd(id, doc.Id); + } + else + { + // Otherwise we have the node id (INT) and can perform an efficient retrieval + doc = cache.GetById(nodeId); + } + return doc; } From f7f42764858e72c0c1b8fabfd58a7a6261e8046c Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 18 Jan 2018 11:44:05 +0000 Subject: [PATCH 09/20] Removed the need for the `UseLegacyXmlSchema` check The enhancement in PR #2367 removed the `"@isDoc"` check, which was the main difference between the legacy and current XML schema. Reducing the XPath to `"//*[@key=$guid]"` will perform the same for both types of schema. _(and saves on a couple of allocations)_ --- src/Umbraco.Web/PublishedContentQuery.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index a609a609b7..73afcc8428 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -243,9 +243,7 @@ namespace Umbraco.Web // If not, then we perform an inefficient XPath for the GUID // todo: in v8, implement in a more efficient way - var legacyXml = UmbracoConfig.For.UmbracoSettings().Content.UseLegacyXmlSchema; - var xpath = legacyXml ? "//node[@key=$guid]" : "//*[@key=$guid]"; - doc = cache.GetSingleByXPath(xpath, new XPathVariable("guid", id.ToString())); + doc = cache.GetSingleByXPath("//*[@key=$guid]", new XPathVariable("guid", id.ToString())); // When we have the node, we add the GUID/INT value to the lookup if (doc != null) From 6841b2ac610f02b3df8640d018574605044925e0 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Fri, 19 Jan 2018 11:12:45 +0000 Subject: [PATCH 10/20] Amended the lookup dictionary setter, as per @JimBobSquarePants's comment: https://github.com/umbraco/Umbraco-CMS/pull/2398#discussion_r162522998 --- src/Umbraco.Web/PublishedContentQuery.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 73afcc8428..4a36f1b3a4 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -247,7 +247,7 @@ namespace Umbraco.Web // When we have the node, we add the GUID/INT value to the lookup if (doc != null) - _guidToIntLoopkup.TryAdd(id, doc.Id); + _guidToIntLoopkup[id] = doc.Id; } else { From a42e568fdfac44613c0b8abd4609737494948df1 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 24 Jan 2018 11:51:53 +0000 Subject: [PATCH 11/20] Initialized the ConcurrentDictionary statically as per @JimBobSquarePants's comment: https://github.com/umbraco/Umbraco-CMS/pull/2398#discussion_r162799543 --- src/Umbraco.Web/PublishedContentQuery.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 4a36f1b3a4..e865f13121 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -227,13 +227,10 @@ namespace Umbraco.Web return doc; } - private static ConcurrentDictionary _guidToIntLoopkup; + private static readonly ConcurrentDictionary _guidToIntLoopkup = new ConcurrentDictionary(); private IPublishedContent TypedDocumentById(Guid id, ContextualPublishedCache cache) { - // We create the lookup when we first need it - if (_guidToIntLoopkup == null) - _guidToIntLoopkup = new ConcurrentDictionary(); IPublishedContent doc; From 0780979469daf33672a66f137cd002a4da57359b Mon Sep 17 00:00:00 2001 From: leekelleher Date: Wed, 24 Jan 2018 11:53:52 +0000 Subject: [PATCH 12/20] Eagerly populating the Guid lookup as per @Shazwazza's comment: https://github.com/umbraco/Umbraco-CMS/pull/2398#issuecomment-358752346 Tested against 12,000 nodes (in the XML cache), profiling showed it took 55ms. --- src/Umbraco.Web/PublishedContentQuery.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index e865f13121..469d53f749 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -231,6 +231,24 @@ namespace Umbraco.Web private IPublishedContent TypedDocumentById(Guid id, ContextualPublishedCache cache) { + // Check if the loopkup is empty, if so populate it [LK] + if (_guidToIntLoopkup.Count == 0) + { + // TODO: Remove the debug profile logger + using (ApplicationContext.Current.ProfilingLogger.DebugDuration("Populate GUID/INT lookup")) + { + // NOTE, using the `@nodeTypeAlias` attribute in the XPath as this was support in both legacy & new schemas + var tmpNodes = cache.GetXPathNavigator().Select("//*[@nodeTypeAlias]"); + foreach (XPathNavigator tmpNode in tmpNodes) + { + if (int.TryParse(tmpNode.GetAttribute("id", string.Empty), out int tmpNodeId) + && Guid.TryParse(tmpNode.GetAttribute("key", string.Empty), out Guid tmpNodeKey)) + { + _guidToIntLoopkup[tmpNodeKey] = tmpNodeId; + } + } + } + } IPublishedContent doc; From c962132ee06b8004ff8ca6e96ca2ec46883ae73f Mon Sep 17 00:00:00 2001 From: Sebastiaan Jansssen Date: Wed, 24 Jan 2018 14:33:32 +0100 Subject: [PATCH 13/20] Update to ModelsBuilder 3.0.9 --- build/NuSpecs/UmbracoCms.nuspec | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- src/Umbraco.Web.UI/packages.config | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index 45009f5f79..e99c38e9c3 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -17,7 +17,7 @@ - + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 97b06153e6..c8532bbe81 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -332,8 +332,8 @@ umbraco.providers - - ..\packages\Umbraco.ModelsBuilder.3.0.8\lib\Umbraco.ModelsBuilder.dll + + ..\packages\Umbraco.ModelsBuilder.3.0.9\lib\Umbraco.ModelsBuilder.dll diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index 5914102315..bd00b80882 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -37,5 +37,5 @@ - + \ No newline at end of file From 7a4b5fa7965ff4ee39170bf174b92a8ec5bee42d Mon Sep 17 00:00:00 2001 From: Sebastiaan Jansssen Date: Wed, 24 Jan 2018 14:34:03 +0100 Subject: [PATCH 14/20] Bump version to 7.7.10 --- src/SolutionInfo.cs | 4 ++-- src/Umbraco.Core/Configuration/UmbracoVersion.cs | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index 1642df09c8..2f9911bfbe 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -11,5 +11,5 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyFileVersion("7.7.9")] -[assembly: AssemblyInformationalVersion("7.7.9")] \ No newline at end of file +[assembly: AssemblyFileVersion("7.7.10")] +[assembly: AssemblyInformationalVersion("7.7.10")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index 5cc63e0835..f4a6dd9377 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.7.9"); + private static readonly Version Version = new Version("7.7.10"); /// /// 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 c8532bbe81..8390d417e1 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -1023,9 +1023,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.1\x86\*.* "$(TargetDir)x86\" True True - 7790 + 7710 / - http://localhost:7790 + http://localhost:7710 False False From 6272dac698e4296c9aeab5e3b65828738c04564f Mon Sep 17 00:00:00 2001 From: Sebastiaan Jansssen Date: Wed, 24 Jan 2018 14:34:21 +0100 Subject: [PATCH 15/20] Use string.Format instead of interpolation --- src/Umbraco.Core/Sync/ApplicationUrlHelper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index 5229ce7dec..57520a3754 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -55,7 +55,7 @@ namespace Umbraco.Core.Sync if (newApplicationUrl) { appContext._umbracoApplicationDomains.Add(applicationUrl); - LogHelper.Info(typeof(ApplicationUrlHelper), $"New ApplicationUrl detected: {applicationUrl}"); + LogHelper.Info(typeof(ApplicationUrlHelper), string.Format("New ApplicationUrl detected: {0}", applicationUrl)); } } @@ -169,4 +169,4 @@ namespace Umbraco.Core.Sync return url.TrimEnd('/'); } } -} \ No newline at end of file +} From abdd11726496e68b1501b547cdc300353907eb1c Mon Sep 17 00:00:00 2001 From: Sebastiaan Jansssen Date: Wed, 24 Jan 2018 15:26:40 +0100 Subject: [PATCH 16/20] Fixes bug in MB 3.0.9 --- build/NuSpecs/UmbracoCms.nuspec | 2 +- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 4 ++-- src/Umbraco.Web.UI/packages.config | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/NuSpecs/UmbracoCms.nuspec b/build/NuSpecs/UmbracoCms.nuspec index e99c38e9c3..2d1b3286f4 100644 --- a/build/NuSpecs/UmbracoCms.nuspec +++ b/build/NuSpecs/UmbracoCms.nuspec @@ -17,7 +17,7 @@ - + diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 8390d417e1..7e215fca26 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -332,8 +332,8 @@ umbraco.providers - - ..\packages\Umbraco.ModelsBuilder.3.0.9\lib\Umbraco.ModelsBuilder.dll + + ..\packages\Umbraco.ModelsBuilder.3.0.10\lib\Umbraco.ModelsBuilder.dll diff --git a/src/Umbraco.Web.UI/packages.config b/src/Umbraco.Web.UI/packages.config index bd00b80882..0fc897d76f 100644 --- a/src/Umbraco.Web.UI/packages.config +++ b/src/Umbraco.Web.UI/packages.config @@ -37,5 +37,5 @@ - + \ No newline at end of file From a288fc62ebf67c7c9cece304cd5ba87d32280540 Mon Sep 17 00:00:00 2001 From: Sebastiaan Jansssen Date: Wed, 24 Jan 2018 17:26:21 +0100 Subject: [PATCH 17/20] Workaround: keep the cache we used to have but clear all cache for membergroups when a member group changes --- .../Repositories/MemberGroupRepository.cs | 23 +++++++++-------- .../Cache/MemberGroupCacheRefresher.cs | 25 +++++++------------ 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs index a477b6fd40..455e2faf38 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberGroupRepository.cs @@ -131,17 +131,18 @@ namespace Umbraco.Core.Persistence.Repositories public IMemberGroup GetByName(string name) { - // cannot use name in the cache key, 'cos when clearing the cache for a given - // member group that has been renamed, we clear the new name but not the old - // name - it just cannot work - and we cannot use the dirty props 'cos it won't - // work on distributed cache - and besides, we end up caching both by name and - // by id - // disabling the cache entirely for now - if we want to cache we'd need to - // implement some sort of cross-reference name/id cache - - var qry = new Query().Where(group => group.Name.Equals(name)); - var result = GetByQuery(qry); - return result.FirstOrDefault(); + return IsolatedCache.GetCacheItem( + string.Format("{0}.{1}", typeof(IMemberGroup).FullName, name), + () => + { + var qry = new Query().Where(group => group.Name.Equals(name)); + var result = GetByQuery(qry); + return result.FirstOrDefault(); + }, + //cache for 5 mins since that is the default in the RuntimeCacheProvider + TimeSpan.FromMinutes(5), + //sliding is true + true); } public IMemberGroup CreateIfNotExists(string roleName) diff --git a/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs b/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs index a27dca47ca..6d3ce6bb57 100644 --- a/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/MemberGroupCacheRefresher.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using System.Web.Script.Serialization; +using System.Web; using Newtonsoft.Json; using Umbraco.Core; using Umbraco.Core.Cache; @@ -83,35 +83,28 @@ namespace Umbraco.Web.Cache public override void Refresh(string jsonPayload) { - ClearCache(DeserializeFromJsonPayload(jsonPayload)); + ClearCache(); base.Refresh(jsonPayload); } public override void Refresh(int id) { - ClearCache(FromMemberGroup(ApplicationContext.Current.Services.MemberGroupService.GetById(id))); + ClearCache(); base.Refresh(id); } public override void Remove(int id) { - ClearCache(FromMemberGroup(ApplicationContext.Current.Services.MemberGroupService.GetById(id))); + ClearCache(); base.Remove(id); } - private void ClearCache(params JsonPayload[] payloads) + private void ClearCache() { - if (payloads == null) return; - - var memberGroupCache = ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.GetCache(); - payloads.ForEach(payload => - { - if (payload != null && memberGroupCache) - { - memberGroupCache.Result.ClearCacheItem(RepositoryBase.GetCacheIdKey(payload.Id)); - } - }); - + // Since we cache by group name, it could be problematic when renaming to + // previously existing names - see http://issues.umbraco.org/issue/U4-10846. + // To work around this, just clear all the cache items + ApplicationContext.Current.ApplicationCache.IsolatedRuntimeCache.ClearCache(); } } } From c623b448baefab9d9e56b211f3c6639a2075b62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Knippers?= Date: Thu, 25 Jan 2018 08:56:46 +0100 Subject: [PATCH 18/20] Fix NullReferenceException when HttpContext is null This method will throw a NullReferenceException in contexts without an HttpContext, like is the case in background threads. As HttpContext is only used to set an isDebug flag, it seems more sensible to not throw in cases without an HttpContext, but simply set isDebug to false instead. If this is not acceptable behavior, could we perhaps look at something other than HttpContext.Current.IsDebuggingEnabled to decide whether isDebug should be set? --- .../PropertyEditors/ValueConverters/GridValueConverter.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs index 85599767d6..e169fa4506 100644 --- a/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs +++ b/src/Umbraco.Core/PropertyEditors/ValueConverters/GridValueConverter.cs @@ -40,12 +40,13 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters //TODO: Change all singleton access to use ctor injection in v8!!! //TODO: That would mean that property value converters would need to be request lifespan, hrm.... + bool isDebug = HttpContext.Current != null && HttpContext.Current.IsDebuggingEnabled; var gridConfig = UmbracoConfig.For.GridConfig( ApplicationContext.Current.ProfilingLogger.Logger, ApplicationContext.Current.ApplicationCache.RuntimeCache, new DirectoryInfo(IOHelper.MapPath(SystemDirectories.AppPlugins)), new DirectoryInfo(IOHelper.MapPath(SystemDirectories.Config)), - HttpContext.Current.IsDebuggingEnabled); + isDebug); var sections = GetArray(obj, "sections"); foreach (var section in sections.Cast()) @@ -112,4 +113,4 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters } } -} \ No newline at end of file +} From 83595ecaf500bcf33ed958d6b15c42721d683b36 Mon Sep 17 00:00:00 2001 From: Sebastiaan Jansssen Date: Mon, 29 Jan 2018 14:06:25 +0100 Subject: [PATCH 19/20] Don't rely on C# 7 features --- src/Umbraco.Core/ObjectExtensions.cs | 86 ++++++++++++++++++---------- 1 file changed, 57 insertions(+), 29 deletions(-) diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 1e3c75ee6d..ccd379f7d2 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -140,7 +140,8 @@ namespace Umbraco.Core if (underlying != null) { // Special case for empty strings for bools/dates which should return null if an empty string. - if (input is string inputString) + var inputString = input as string; + if (inputString != null) { if (string.IsNullOrEmpty(inputString) && (underlying == typeof(DateTime) || underlying == typeof(bool))) { @@ -166,7 +167,8 @@ namespace Umbraco.Core { // target is not a generic type - if (input is string inputString) + var inputString = input as string; + if (inputString != null) { // Try convert from string, returns an Attempt if the string could be // processed (either succeeded or failed), else null if we need to try @@ -207,7 +209,8 @@ namespace Umbraco.Core } // Re-check convertables since we altered the input through recursion - if (input is IConvertible convertible2) + var convertible2 = input as IConvertible; + if (convertible2 != null) { return Attempt.Succeed(Convert.ChangeType(convertible2, target)); } @@ -265,7 +268,8 @@ namespace Umbraco.Core { if (target == typeof(int)) { - if (int.TryParse(input, out var value)) + int value; + if (int.TryParse(input, out value)) { return Attempt.Succeed(value); } @@ -273,26 +277,30 @@ namespace Umbraco.Core // Because decimal 100.01m will happily convert to integer 100, it // makes sense that string "100.01" *also* converts to integer 100. var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt32(value2)); + decimal value2; + return Attempt.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt32(value2)); } if (target == typeof(long)) { - if (long.TryParse(input, out var value)) + long value; + if (long.TryParse(input, out value)) { return Attempt.Succeed(value); } // Same as int var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(decimal.TryParse(input2, out var value2), Convert.ToInt64(value2)); + decimal value2; + return Attempt.SucceedIf(decimal.TryParse(input2, out value2), Convert.ToInt64(value2)); } // TODO: Should we do the decimal trick for short, byte, unsigned? if (target == typeof(bool)) { - if (bool.TryParse(input, out var value)) + bool value; + if (bool.TryParse(input, out value)) { return Attempt.Succeed(value); } @@ -305,42 +313,53 @@ namespace Umbraco.Core switch (Type.GetTypeCode(target)) { case TypeCode.Int16: - return Attempt.SucceedIf(short.TryParse(input, out var value), value); + short value; + return Attempt.SucceedIf(short.TryParse(input, out value), value); case TypeCode.Double: var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(double.TryParse(input2, out var valueD), valueD); + double valueD; + return Attempt.SucceedIf(double.TryParse(input2, out valueD), valueD); case TypeCode.Single: var input3 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(float.TryParse(input3, out var valueF), valueF); + float valueF; + return Attempt.SucceedIf(float.TryParse(input3, out valueF), valueF); case TypeCode.Char: - return Attempt.SucceedIf(char.TryParse(input, out var valueC), valueC); + char valueC; + return Attempt.SucceedIf(char.TryParse(input, out valueC), valueC); case TypeCode.Byte: - return Attempt.SucceedIf(byte.TryParse(input, out var valueB), valueB); + byte valueB; + return Attempt.SucceedIf(byte.TryParse(input, out valueB), valueB); case TypeCode.SByte: - return Attempt.SucceedIf(sbyte.TryParse(input, out var valueSb), valueSb); + sbyte valueSb; + return Attempt.SucceedIf(sbyte.TryParse(input, out valueSb), valueSb); case TypeCode.UInt32: - return Attempt.SucceedIf(uint.TryParse(input, out var valueU), valueU); + uint valueU; + return Attempt.SucceedIf(uint.TryParse(input, out valueU), valueU); case TypeCode.UInt16: - return Attempt.SucceedIf(ushort.TryParse(input, out var valueUs), valueUs); + ushort valueUs; + return Attempt.SucceedIf(ushort.TryParse(input, out valueUs), valueUs); case TypeCode.UInt64: - return Attempt.SucceedIf(ulong.TryParse(input, out var valueUl), valueUl); + ulong valueUl; + return Attempt.SucceedIf(ulong.TryParse(input, out valueUl), valueUl); } } else if (target == typeof(Guid)) { - return Attempt.SucceedIf(Guid.TryParse(input, out var value), value); + Guid value; + return Attempt.SucceedIf(Guid.TryParse(input, out value), value); } else if (target == typeof(DateTime)) { - if (DateTime.TryParse(input, out var value)) + DateTime value; + if (DateTime.TryParse(input, out value)) { switch (value.Kind) { @@ -360,20 +379,24 @@ namespace Umbraco.Core } else if (target == typeof(DateTimeOffset)) { - return Attempt.SucceedIf(DateTimeOffset.TryParse(input, out var value), value); + DateTimeOffset value; + return Attempt.SucceedIf(DateTimeOffset.TryParse(input, out value), value); } else if (target == typeof(TimeSpan)) { - return Attempt.SucceedIf(TimeSpan.TryParse(input, out var value), value); + TimeSpan value; + return Attempt.SucceedIf(TimeSpan.TryParse(input, out value), value); } else if (target == typeof(decimal)) { var input2 = NormalizeNumberDecimalSeparator(input); - return Attempt.SucceedIf(decimal.TryParse(input2, out var value), value); + decimal value; + return Attempt.SucceedIf(decimal.TryParse(input2, out value), value); } else if (input != null && target == typeof(Version)) { - return Attempt.SucceedIf(Version.TryParse(input, out var value), value); + Version value; + return Attempt.SucceedIf(Version.TryParse(input, out value), value); } // E_NOTIMPL IPAddress, BigInteger @@ -658,7 +681,8 @@ namespace Umbraco.Core { var key = new CompositeTypeTypeKey(source, target); - if (InputTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter)) + TypeConverter typeConverter; + if (InputTypeConverterCache.TryGetValue(key, out typeConverter)) { return typeConverter; } @@ -678,7 +702,8 @@ namespace Umbraco.Core { var key = new CompositeTypeTypeKey(source, target); - if (DestinationTypeConverterCache.TryGetValue(key, out TypeConverter typeConverter)) + TypeConverter typeConverter; + if (DestinationTypeConverterCache.TryGetValue(key, out typeConverter)) { return typeConverter; } @@ -696,7 +721,8 @@ namespace Umbraco.Core [MethodImpl(MethodImplOptions.AggressiveInlining)] private static Type GetCachedGenericNullableType(Type type) { - if (NullableGenericCache.TryGetValue(type, out Type underlyingType)) + Type underlyingType; + if (NullableGenericCache.TryGetValue(type, out underlyingType)) { return underlyingType; } @@ -715,7 +741,8 @@ namespace Umbraco.Core private static bool GetCachedCanAssign(object input, Type source, Type target) { var key = new CompositeTypeTypeKey(source, target); - if (AssignableTypeCache.TryGetValue(key, out bool canConvert)) + bool canConvert; + if (AssignableTypeCache.TryGetValue(key, out canConvert)) { return canConvert; } @@ -734,7 +761,8 @@ namespace Umbraco.Core [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool GetCachedCanConvertToBoolean(Type type) { - if (BoolConvertCache.TryGetValue(type, out bool result)) + bool result; + if (BoolConvertCache.TryGetValue(type, out result)) { return result; } @@ -747,4 +775,4 @@ namespace Umbraco.Core return BoolConvertCache[type] = false; } } -} \ No newline at end of file +} From 7cd540c37ea97d6f3ef2fdc6189cb02cb3fc3b25 Mon Sep 17 00:00:00 2001 From: Sebastiaan Jansssen Date: Mon, 29 Jan 2018 14:35:30 +0100 Subject: [PATCH 20/20] Remove additional premature uses of C#7 features --- .../Collections/CompositeTypeTypeKey.cs | 23 +++++++++++++------ .../ConcurrentDictionaryBenchmarks.cs | 3 ++- src/Umbraco.Web/PublishedContentQuery.cs | 13 +++++++---- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs index 07c9a8ded2..1a4e7ae1a9 100644 --- a/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs +++ b/src/Umbraco.Core/Collections/CompositeTypeTypeKey.cs @@ -10,7 +10,7 @@ namespace Umbraco.Core.Collections /// /// Initializes a new instance of the struct. /// - public CompositeTypeTypeKey(Type type1, Type type2) + public CompositeTypeTypeKey(Type type1, Type type2) : this() { Type1 = type1; Type2 = type2; @@ -19,26 +19,35 @@ namespace Umbraco.Core.Collections /// /// Gets the first type. /// - public Type Type1 { get; } + public Type Type1 { get; private set; } /// /// Gets the second type. /// - public Type Type2 { get; } + public Type Type2 { get; private set; } /// public bool Equals(CompositeTypeTypeKey other) - => Type1 == other.Type1 && Type2 == other.Type2; + { + return Type1 == other.Type1 && Type2 == other.Type2; + } /// public override bool Equals(object obj) - => obj is CompositeTypeTypeKey other && Type1 == other.Type1 && Type2 == other.Type2; + { + var other = obj is CompositeTypeTypeKey ? (CompositeTypeTypeKey)obj : default(CompositeTypeTypeKey); + return Type1 == other.Type1 && Type2 == other.Type2; + } public static bool operator ==(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) - => key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2; + { + return key1.Type1 == key2.Type1 && key1.Type2 == key2.Type2; + } public static bool operator !=(CompositeTypeTypeKey key1, CompositeTypeTypeKey key2) - => key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2; + { + return key1.Type1 != key2.Type1 || key1.Type2 != key2.Type2; + } /// public override int GetHashCode() diff --git a/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.cs b/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.cs index 6cb39b7235..4e8476bb6d 100644 --- a/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.cs +++ b/src/Umbraco.Tests.Benchmarks/ConcurrentDictionaryBenchmarks.cs @@ -37,7 +37,8 @@ namespace Umbraco.Tests.Benchmarks { // This method is 10% faster var key = new CompositeTypeTypeKey(source, target); - if (AssignableTypeCache.TryGetValue(key, out bool canConvert)) + bool canConvert; + if (AssignableTypeCache.TryGetValue(key, out canConvert)) { return canConvert; } diff --git a/src/Umbraco.Web/PublishedContentQuery.cs b/src/Umbraco.Web/PublishedContentQuery.cs index 469d53f749..6cd130af0a 100644 --- a/src/Umbraco.Web/PublishedContentQuery.cs +++ b/src/Umbraco.Web/PublishedContentQuery.cs @@ -241,8 +241,10 @@ namespace Umbraco.Web var tmpNodes = cache.GetXPathNavigator().Select("//*[@nodeTypeAlias]"); foreach (XPathNavigator tmpNode in tmpNodes) { - if (int.TryParse(tmpNode.GetAttribute("id", string.Empty), out int tmpNodeId) - && Guid.TryParse(tmpNode.GetAttribute("key", string.Empty), out Guid tmpNodeKey)) + int tmpNodeId; + Guid tmpNodeKey; + if (int.TryParse(tmpNode.GetAttribute("id", string.Empty), out tmpNodeId) + && Guid.TryParse(tmpNode.GetAttribute("key", string.Empty), out tmpNodeKey)) { _guidToIntLoopkup[tmpNodeKey] = tmpNodeId; } @@ -252,8 +254,9 @@ namespace Umbraco.Web IPublishedContent doc; - // Check if the lookup contains the GUID/INT value - if (_guidToIntLoopkup.TryGetValue(id, out int nodeId) == false) + // Check if the lookup contains the GUID/INT value + int nodeId; + if (_guidToIntLoopkup.TryGetValue(id, out nodeId) == false) { // If not, then we perform an inefficient XPath for the GUID @@ -460,4 +463,4 @@ namespace Umbraco.Web #endregion } -} \ No newline at end of file +}